Skip to content

Commit e80c0ab

Browse files
mariofuscoaloubyansky
authored andcommitted
Follow up of the fix making jar file reference close idempotent with minor comments and refactor
(cherry picked from commit b8e7cd8)
1 parent c543035 commit e80c0ab

File tree

1 file changed

+23
-32
lines changed

1 file changed

+23
-32
lines changed

independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/runner/JarFileReference.java

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import java.io.IOException;
66
import java.nio.file.Path;
7-
import java.util.Objects;
87
import java.util.concurrent.CompletableFuture;
98
import java.util.concurrent.atomic.AtomicInteger;
109
import java.util.jar.JarFile;
@@ -14,7 +13,8 @@
1413
public class JarFileReference {
1514

1615
// 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+
1818
// Guarded by an atomic reader counter that emulate the behaviour of a read/write lock.
1919
// To enable virtual threads compatibility and avoid pinning it is not possible to use an explicit read/write lock
2020
// because the jarFile access may happen inside a native call (for example triggered by the RunnerClassLoader)
@@ -26,22 +26,10 @@ public class JarFileReference {
2626
// The JarFileReference is created as already acquired and that's why the referenceCounter starts from 2
2727
private final AtomicInteger referenceCounter = new AtomicInteger(2);
2828

29-
private JarFileReference(JarFile jarFile) {
29+
private JarFileReference(JarFile jarFile, CompletableFuture<JarFileReference> completedFuture) {
3030
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);
4533
}
4634

4735
/**
@@ -57,21 +45,20 @@ private boolean acquire() {
5745
if (count == 0) {
5846
return false;
5947
}
60-
if (referenceCounter.compareAndSet(count, addCount(count, 1))) {
48+
if (referenceCounter.compareAndSet(count, changeReferenceCount(count, 1))) {
6149
return true;
6250
}
6351
}
6452
}
6553

6654
/**
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.
6858
*/
69-
private static int addCount(final int count, int delta) {
59+
private static int changeReferenceCount(final int count, int delta) {
7060
assert count != 0;
71-
if (count < 0) {
72-
delta = -delta;
73-
}
74-
return count + delta;
61+
return count < 0 ? count - delta : count + delta;
7562
}
7663

7764
/**
@@ -89,17 +76,18 @@ private boolean release(JarResource jarResource) {
8976
if (count == 1 || count == 0) {
9077
throw new IllegalStateException("Duplicate release? The reference counter cannot be " + count);
9178
}
92-
if (referenceCounter.compareAndSet(count, addCount(count, -1))) {
79+
if (referenceCounter.compareAndSet(count, changeReferenceCount(count, -1))) {
9380
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);
9583
return true;
9684
}
9785
return false;
9886
}
9987
}
10088
}
10189

102-
private void silentCloseJarResources(JarResource jarResource) {
90+
private void closeJarResources(JarResource jarResource) {
10391
// we need to make sure we're not deleting others state
10492
jarResource.jarFileReference.compareAndSet(completedFuture, null);
10593
try {
@@ -110,7 +98,7 @@ private void silentCloseJarResources(JarResource jarResource) {
11098
}
11199

112100
/**
113-
* Ask to close this reference.
101+
* Mark this jar reference as ready to be closed.
114102
* If there are no readers currently accessing the jarFile also close it, otherwise defer the closing when the last reader
115103
* will leave.
116104
*/
@@ -122,9 +110,10 @@ void markForClosing(JarResource jarResource) {
122110
return;
123111
}
124112
// 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))) {
126115
if (count == 1) {
127-
silentCloseJarResources(jarResource);
116+
closeJarResources(jarResource);
128117
}
129118
}
130119
}
@@ -145,6 +134,7 @@ static <T> T withJarFile(JarResource jarResource, String resource, JarFileConsum
145134
if (jarFileReference.acquire()) {
146135
return consumeSharedJarFile(jarFileReference, jarResource, resource, fileConsumer);
147136
}
137+
// The acquire failure implies that the reference is already marked to be closed.
148138
closingLocalJarFileRef = true;
149139
}
150140

@@ -199,7 +189,8 @@ private static <T> T consumeUnsharedJarFile(CompletableFuture<JarFileReference>
199189

200190
private static CompletableFuture<JarFileReference> syncLoadAcquiredJarFile(JarResource jarResource) {
201191
try {
202-
return JarFileReference.completedWith(JarFiles.create(jarResource.jarPath.toFile()));
192+
return new JarFileReference(JarFiles.create(jarResource.jarPath.toFile()),
193+
new CompletableFuture<>()).completedFuture;
203194
} catch (IOException e) {
204195
throw new RuntimeException("Failed to open " + jarResource.jarPath, e);
205196
}
@@ -213,7 +204,7 @@ private static JarFileReference asyncLoadAcquiredJarFile(JarResource jarResource
213204
do {
214205
if (jarResource.jarFileReference.compareAndSet(null, newJarRefFuture)) {
215206
try {
216-
return JarFileReference.completeWith(newJarRefFuture, JarFiles.create(jarResource.jarPath.toFile()));
207+
return new JarFileReference(JarFiles.create(jarResource.jarPath.toFile()), newJarRefFuture);
217208
} catch (IOException e) {
218209
throw new RuntimeException(e);
219210
}

0 commit comments

Comments
 (0)