4
4
5
5
import java .io .IOException ;
6
6
import java .nio .file .Path ;
7
- import java .util .Objects ;
8
7
import java .util .concurrent .CompletableFuture ;
9
8
import java .util .concurrent .atomic .AtomicInteger ;
10
9
import java .util .jar .JarFile ;
14
13
public class JarFileReference {
15
14
16
15
// This is required to perform cleanup of JarResource::jarFileReference without breaking racy updates
17
- private CompletableFuture <JarFileReference > completedFuture ;
16
+ private final CompletableFuture <JarFileReference > completedFuture ;
17
+
18
18
// Guarded by an atomic reader counter that emulate the behaviour of a read/write lock.
19
19
// To enable virtual threads compatibility and avoid pinning it is not possible to use an explicit read/write lock
20
20
// because the jarFile access may happen inside a native call (for example triggered by the RunnerClassLoader)
@@ -26,22 +26,10 @@ public class JarFileReference {
26
26
// The JarFileReference is created as already acquired and that's why the referenceCounter starts from 2
27
27
private final AtomicInteger referenceCounter = new AtomicInteger (2 );
28
28
29
- private JarFileReference (JarFile jarFile ) {
29
+ private JarFileReference (JarFile jarFile , CompletableFuture < JarFileReference > completedFuture ) {
30
30
this .jarFile = jarFile ;
31
- }
32
-
33
- public static JarFileReference completeWith (CompletableFuture <JarFileReference > completableFuture , JarFile jarFile ) {
34
- Objects .requireNonNull (completableFuture );
35
- var jarFileRef = new JarFileReference (jarFile );
36
- jarFileRef .completedFuture = completableFuture ;
37
- completableFuture .complete (jarFileRef );
38
- return jarFileRef ;
39
- }
40
-
41
- public static CompletableFuture <JarFileReference > completedWith (JarFile jarFile ) {
42
- var jarFileRef = new JarFileReference (jarFile );
43
- jarFileRef .completedFuture = CompletableFuture .completedFuture (jarFileRef );
44
- return jarFileRef .completedFuture ;
31
+ this .completedFuture = completedFuture ;
32
+ this .completedFuture .complete (this );
45
33
}
46
34
47
35
/**
@@ -57,21 +45,20 @@ private boolean acquire() {
57
45
if (count == 0 ) {
58
46
return false ;
59
47
}
60
- if (referenceCounter .compareAndSet (count , addCount (count , 1 ))) {
48
+ if (referenceCounter .compareAndSet (count , changeReferenceCount (count , 1 ))) {
61
49
return true ;
62
50
}
63
51
}
64
52
}
65
53
66
54
/**
67
- * This is not allowed to change the sign of count (unless put it to 0)
55
+ * Change the absolute value of the provided reference count of the given delta, that can only be 1 when the reference is
56
+ * acquired by a new reader or -1 when the reader releases the reference or the reference itself is marked for closing.
57
+ * A negative reference count means that this reference has been marked for closing.
68
58
*/
69
- private static int addCount (final int count , int delta ) {
59
+ private static int changeReferenceCount (final int count , int delta ) {
70
60
assert count != 0 ;
71
- if (count < 0 ) {
72
- delta = -delta ;
73
- }
74
- return count + delta ;
61
+ return count < 0 ? count - delta : count + delta ;
75
62
}
76
63
77
64
/**
@@ -89,17 +76,18 @@ private boolean release(JarResource jarResource) {
89
76
if (count == 1 || count == 0 ) {
90
77
throw new IllegalStateException ("Duplicate release? The reference counter cannot be " + count );
91
78
}
92
- if (referenceCounter .compareAndSet (count , addCount (count , -1 ))) {
79
+ if (referenceCounter .compareAndSet (count , changeReferenceCount (count , -1 ))) {
93
80
if (count == -1 ) {
94
- silentCloseJarResources (jarResource );
81
+ // The reference has been already marked to be closed (the counter is negative) and this is the last reader releasing it
82
+ closeJarResources (jarResource );
95
83
return true ;
96
84
}
97
85
return false ;
98
86
}
99
87
}
100
88
}
101
89
102
- private void silentCloseJarResources (JarResource jarResource ) {
90
+ private void closeJarResources (JarResource jarResource ) {
103
91
// we need to make sure we're not deleting others state
104
92
jarResource .jarFileReference .compareAndSet (completedFuture , null );
105
93
try {
@@ -110,7 +98,7 @@ private void silentCloseJarResources(JarResource jarResource) {
110
98
}
111
99
112
100
/**
113
- * Ask to close this reference.
101
+ * Mark this jar reference as ready to be closed .
114
102
* If there are no readers currently accessing the jarFile also close it, otherwise defer the closing when the last reader
115
103
* will leave.
116
104
*/
@@ -122,9 +110,10 @@ void markForClosing(JarResource jarResource) {
122
110
return ;
123
111
}
124
112
// close must change the value into a negative one or zeroing
125
- if (referenceCounter .compareAndSet (count , addCount (-count , -1 ))) {
113
+ // the reference counter is turned into a negative value to indicate (in an idempotent way) that the resource has been marked to be closed.
114
+ if (referenceCounter .compareAndSet (count , changeReferenceCount (-count , -1 ))) {
126
115
if (count == 1 ) {
127
- silentCloseJarResources (jarResource );
116
+ closeJarResources (jarResource );
128
117
}
129
118
}
130
119
}
@@ -145,6 +134,7 @@ static <T> T withJarFile(JarResource jarResource, String resource, JarFileConsum
145
134
if (jarFileReference .acquire ()) {
146
135
return consumeSharedJarFile (jarFileReference , jarResource , resource , fileConsumer );
147
136
}
137
+ // The acquire failure implies that the reference is already marked to be closed.
148
138
closingLocalJarFileRef = true ;
149
139
}
150
140
@@ -199,7 +189,8 @@ private static <T> T consumeUnsharedJarFile(CompletableFuture<JarFileReference>
199
189
200
190
private static CompletableFuture <JarFileReference > syncLoadAcquiredJarFile (JarResource jarResource ) {
201
191
try {
202
- return JarFileReference .completedWith (JarFiles .create (jarResource .jarPath .toFile ()));
192
+ return new JarFileReference (JarFiles .create (jarResource .jarPath .toFile ()),
193
+ new CompletableFuture <>()).completedFuture ;
203
194
} catch (IOException e ) {
204
195
throw new RuntimeException ("Failed to open " + jarResource .jarPath , e );
205
196
}
@@ -213,7 +204,7 @@ private static JarFileReference asyncLoadAcquiredJarFile(JarResource jarResource
213
204
do {
214
205
if (jarResource .jarFileReference .compareAndSet (null , newJarRefFuture )) {
215
206
try {
216
- return JarFileReference . completeWith ( newJarRefFuture , JarFiles .create (jarResource .jarPath .toFile ()));
207
+ return new JarFileReference ( JarFiles .create (jarResource .jarPath .toFile ()), newJarRefFuture );
217
208
} catch (IOException e ) {
218
209
throw new RuntimeException (e );
219
210
}
0 commit comments