Skip to content

Commit 5736455

Browse files
committed
Do not use cached classpath elements when initializing deployment and
runtime classloaders
1 parent bcb3044 commit 5736455

File tree

6 files changed

+121
-50
lines changed

6 files changed

+121
-50
lines changed

independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/DirectoryPathTree.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import java.io.IOException;
44
import java.io.Serializable;
55
import java.nio.file.Path;
6-
import java.nio.file.Paths;
6+
import java.util.Objects;
77

88
public class DirectoryPathTree extends OpenContainerPathTree implements Serializable {
99

@@ -28,12 +28,12 @@ public DirectoryPathTree(Path dir, PathFilter pathFilter) {
2828

2929
public DirectoryPathTree(Path dir, PathFilter pathFilter, boolean manifestEnabled) {
3030
super(pathFilter, manifestEnabled);
31-
this.dir = dir;
31+
this.dir = Objects.requireNonNull(dir, "Directory is null");
3232
}
3333

3434
protected DirectoryPathTree(Path dir, PathFilter pathFilter, PathTreeWithManifest pathTreeWithManifest) {
3535
super(pathFilter, pathTreeWithManifest);
36-
this.dir = dir;
36+
this.dir = Objects.requireNonNull(dir, "Directory is null");
3737
}
3838

3939
@Override
@@ -72,7 +72,7 @@ private void writeObject(java.io.ObjectOutputStream out) throws IOException {
7272
}
7373

7474
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
75-
dir = Paths.get(in.readUTF());
75+
dir = Path.of(in.readUTF());
7676
pathFilter = (PathFilter) in.readObject();
7777
manifestEnabled = in.readBoolean();
7878
}

independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/OpenContainerPathTree.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,11 @@ public OpenPathTree open() {
9393

9494
@Override
9595
public Collection<Path> getRoots() {
96-
return List.of(getRootPath());
96+
final Path rootPath = getRootPath();
97+
if (rootPath == null) {
98+
throw new RuntimeException("rootPath is null for " + getContainerPath());
99+
}
100+
return List.of(rootPath);
97101
}
98102

99103
@Override

independent-projects/bootstrap/app-model/src/main/java/io/quarkus/paths/SharedArchivePathTree.java

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,11 @@ static void removeFromCache(Path path) {
5353
@Override
5454
public OpenPathTree open() {
5555
var lastOpen = this.lastOpen;
56-
if (lastOpen != null && lastOpen.acquire()) {
57-
return new CallerOpenPathTree(lastOpen);
56+
if (lastOpen != null) {
57+
var acquired = lastOpen.acquire();
58+
if (acquired != null) {
59+
return acquired;
60+
}
5861
}
5962
try {
6063
this.lastOpen = new SharedOpenArchivePathTree(openFs());
@@ -73,14 +76,28 @@ protected SharedOpenArchivePathTree(FileSystem fs) {
7376
openCount.incrementAndGet();
7477
}
7578

76-
private boolean acquire() {
79+
/**
80+
* Returns a new handle for this open archive tree to the caller
81+
* as long as this open archive tree is still open and is still
82+
* the last one that was open for this archive. Otherwise, the method
83+
* will return null.
84+
*
85+
* @return a new instance of {@link CallerOpenPathTree} or null,
86+
* if the current open archive tree has been closed or another open
87+
* archive tree has been created for this archive
88+
*/
89+
private CallerOpenPathTree acquire() {
7790
readLock().lock();
78-
final boolean result = lastOpen == this && isOpen();
79-
if (result) {
80-
users.incrementAndGet();
91+
try {
92+
final boolean result = lastOpen == this && isOpen();
93+
if (result) {
94+
users.incrementAndGet();
95+
return new CallerOpenPathTree(this);
96+
}
97+
} finally {
98+
readLock().unlock();
8199
}
82-
readLock().unlock();
83-
return result;
100+
return null;
84101
}
85102

86103
@Override

independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/app/CuratedApplication.java

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,22 @@ private Object runInCl(String consumerName, Map<String, Object> params, QuarkusC
146146
}
147147
}
148148

149-
private synchronized void processCpElement(ResolvedDependency artifact, Consumer<ClassPathElement> consumer) {
149+
/**
150+
* Turns a resolved dependency into a classpath element and calls a consumer to process it.
151+
* The consumer will add the classpath element to a {@link QuarkusClassLoader} instance.
152+
*
153+
* TODO: useCpeCache argument is a quick workaround to avoid the risk of sharing classpath elements
154+
* among multiple instances of deployment and runtime classloaders. When these shared classpath elements
155+
* happen to be JARs, when the first deployment classloader is closed, its JAR classpath elements will
156+
* get closed as well but will remain cached, so the next deployment classloader will be initialized
157+
* with the closed JAR classpath elements.
158+
*
159+
* @param artifact resolved dependency
160+
* @param consumer classpath element consumer
161+
* @param useCpeCache whether to re-use cached classpath elements
162+
*/
163+
private synchronized void processCpElement(ResolvedDependency artifact, Consumer<ClassPathElement> consumer,
164+
boolean useCpeCache) {
150165
if (!artifact.isJar()) {
151166
//avoid the need for this sort of check in multiple places
152167
consumer.accept(ClassPathElement.EMPTY);
@@ -162,7 +177,7 @@ public void accept(ClassPathElement classPathElement) {
162177
}
163178
};
164179
}
165-
ClassPathElement cpe = augmentationElements.get(artifact.getKey());
180+
ClassPathElement cpe = useCpeCache ? augmentationElements.get(artifact.getKey()) : null;
166181
if (cpe != null) {
167182
consumer.accept(cpe);
168183
return;
@@ -172,9 +187,11 @@ public void accept(ClassPathElement classPathElement) {
172187
consumer.accept(ClassPathElement.EMPTY);
173188
return;
174189
}
175-
cpe = ClassPathElement.fromDependency(artifact);
190+
cpe = ClassPathElement.fromDependency(contentTree, artifact);
176191
consumer.accept(cpe);
177-
augmentationElements.put(artifact.getKey(), cpe);
192+
if (useCpeCache) {
193+
augmentationElements.put(artifact.getKey(), cpe);
194+
}
178195
}
179196

180197
private void addCpElement(QuarkusClassLoader.Builder builder, ResolvedDependency dep, ClassPathElement element) {
@@ -204,13 +221,13 @@ public synchronized QuarkusClassLoader getOrCreateAugmentClassLoader() {
204221
//this will load any deployment artifacts from the parent CL if they are present
205222
for (ResolvedDependency i : appModel.getDependencies()) {
206223
if (configuredClassLoading.isRemovedArtifact(i.getKey())) {
207-
processCpElement(i, builder::addBannedElement);
224+
processCpElement(i, builder::addBannedElement, true);
208225
continue;
209226
}
210227
if (configuredClassLoading.isReloadableArtifact(i.getKey())) {
211228
continue;
212229
}
213-
processCpElement(i, element -> addCpElement(builder, i, element));
230+
processCpElement(i, element -> addCpElement(builder, i, element), true);
214231
}
215232

216233
for (Path i : quarkusBootstrap.getAdditionalDeploymentArchives()) {
@@ -297,7 +314,7 @@ public synchronized QuarkusClassLoader getOrCreateBaseRuntimeClassLoader() {
297314

298315
for (ResolvedDependency dependency : appModel.getDependencies()) {
299316
if (configuredClassLoading.isRemovedArtifact(dependency.getKey())) {
300-
processCpElement(dependency, builder::addBannedElement);
317+
processCpElement(dependency, builder::addBannedElement, true);
301318
continue;
302319
}
303320
if (!dependency.isRuntimeCp()
@@ -307,7 +324,7 @@ public synchronized QuarkusClassLoader getOrCreateBaseRuntimeClassLoader() {
307324
&& appModel.getReloadableWorkspaceDependencies().contains(dependency.getKey())) {
308325
continue;
309326
}
310-
processCpElement(dependency, element -> addCpElement(builder, dependency, element));
327+
processCpElement(dependency, element -> addCpElement(builder, dependency, element), true);
311328
}
312329

313330
baseRuntimeClassLoader = builder.build();
@@ -358,7 +375,7 @@ public QuarkusClassLoader createDeploymentClassLoader() {
358375
continue;
359376
}
360377
if (isReloadableRuntimeDependency(dependency)) {
361-
processCpElement(dependency, element -> addCpElement(builder, dependency, element));
378+
processCpElement(dependency, element -> addCpElement(builder, dependency, element), false);
362379
}
363380
}
364381
for (Path root : configuredClassLoading.getAdditionalClasspathElements()) {
@@ -389,7 +406,7 @@ public QuarkusClassLoader createRuntimeClassLoader(ClassLoader base, Map<String,
389406
+ getClassLoaderNameSuffix()
390407
+ " restart no:"
391408
+ runtimeClassLoaderCount.getAndIncrement(),
392-
getOrCreateBaseRuntimeClassLoader(), false)
409+
base, false)
393410
.setAssertionsEnabled(quarkusBootstrap.isAssertionsEnabled())
394411
.setCuratedApplication(this)
395412
.setAggregateParentResources(true);
@@ -412,7 +429,7 @@ public QuarkusClassLoader createRuntimeClassLoader(ClassLoader base, Map<String,
412429
continue;
413430
}
414431
if (isReloadableRuntimeDependency(dependency)) {
415-
processCpElement(dependency, element -> addCpElement(builder, dependency, element));
432+
processCpElement(dependency, element -> addCpElement(builder, dependency, element), false);
416433
}
417434
}
418435
for (Path root : configuredClassLoading.getAdditionalClasspathElements()) {

independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/ClassPathElement.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,14 @@ static ClassPathElement fromPath(Path path, boolean runtime) {
102102
}
103103

104104
static ClassPathElement fromDependency(ResolvedDependency dep) {
105-
return new PathTreeClassPathElement(dep.getContentTree(), dep.isRuntimeCp(), dep);
105+
return fromDependency(dep.getContentTree(), dep);
106106
}
107107

108-
static ClassPathElement EMPTY = new ClassPathElement() {
108+
static ClassPathElement fromDependency(PathTree contentTree, ResolvedDependency dep) {
109+
return new PathTreeClassPathElement(contentTree, dep.isRuntimeCp(), dep);
110+
}
111+
112+
ClassPathElement EMPTY = new ClassPathElement() {
109113

110114
@Override
111115
public Path getRoot() {

independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/PathTreeClassPathElement.java

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import io.quarkus.paths.OpenPathTree;
2929
import io.quarkus.paths.PathTree;
3030
import io.quarkus.paths.PathVisit;
31-
import io.quarkus.paths.PathVisitor;
3231

3332
public class PathTreeClassPathElement extends AbstractClassPathElement {
3433

@@ -104,7 +103,11 @@ public ClassPathResource getResource(String name) {
104103
if (resources != null && !resources.contains(sanitized)) {
105104
return null;
106105
}
107-
return apply(tree -> tree.apply(sanitized, visit -> visit == null ? null : new Resource(visit)));
106+
return apply(tree -> tree.apply(sanitized, this::getResource));
107+
}
108+
109+
private Resource getResource(PathVisit visit) {
110+
return visit == null ? null : new Resource(visit);
108111
}
109112

110113
@Override
@@ -114,17 +117,15 @@ public List<ClassPathResource> getResources(String name) {
114117
if (resources != null && !resources.contains(sanitized)) {
115118
return List.of();
116119
}
117-
List<ClassPathResource> ret = new ArrayList<>();
118-
apply(tree -> {
120+
return apply(tree -> {
121+
final List<ClassPathResource> ret = new ArrayList<>();
119122
tree.acceptAll(sanitized, visit -> {
120123
if (visit != null) {
121124
ret.add(new Resource(visit));
122-
123125
}
124126
});
125-
return List.of();
127+
return ret;
126128
});
127-
return ret;
128129
}
129130

130131
@Override
@@ -155,25 +156,23 @@ public <T> T apply(Function<OpenPathTree, T> func) {
155156
public Set<String> getProvidedResources() {
156157
Set<String> resources = this.resources;
157158
if (resources == null) {
158-
resources = apply(tree -> {
159-
final Set<String> relativePaths = new HashSet<>();
160-
tree.walk(new PathVisitor() {
161-
@Override
162-
public void visitPath(PathVisit visit) {
163-
final String relativePath = visit.getRelativePath("/");
164-
if (relativePath.isEmpty()) {
165-
return;
166-
}
167-
relativePaths.add(relativePath);
168-
}
169-
});
170-
return relativePaths;
171-
});
159+
resources = apply(PathTreeClassPathElement::collectResources);
172160
this.resources = resources;
173161
}
174162
return resources;
175163
}
176164

165+
private static Set<String> collectResources(OpenPathTree tree) {
166+
final Set<String> relativePaths = new HashSet<>();
167+
tree.walk(visit -> {
168+
final String relativePath = visit.getRelativePath();
169+
if (!relativePath.isEmpty()) {
170+
relativePaths.add(relativePath);
171+
}
172+
});
173+
return relativePaths;
174+
}
175+
177176
@Override
178177
public boolean containsReloadableResources() {
179178
return !pathTree.isArchiveOrigin();
@@ -229,7 +228,7 @@ private class Resource implements ClassPathResource {
229228
private volatile URL url;
230229

231230
private Resource(PathVisit visit) {
232-
name = visit.getRelativePath("/");
231+
name = visit.getRelativePath();
233232
path = visit.getPath();
234233
}
235234

@@ -271,14 +270,36 @@ public URL getUrl() {
271270
}
272271
return this.url = url;
273272
}
274-
return url = apply(tree -> tree.apply(name, visit -> visit == null ? null : visit.getUrl()));
273+
return url = apply(this::getUrl);
275274
} catch (MalformedURLException e) {
276275
throw new RuntimeException("Failed to translate " + path + " to URL", e);
277276
} finally {
278277
lock.readLock().unlock();
279278
}
280279
}
281280

281+
/**
282+
* URL for this resource in a given {@link OpenPathTree}.
283+
* If the path tree contains the resource, its URL is returned.
284+
* Otherwise, the method returns null.
285+
*
286+
* @param tree path tree to look for the resource in
287+
* @return resource URL if it exists in a given path tree or null if it does not
288+
*/
289+
private URL getUrl(OpenPathTree tree) {
290+
return tree.apply(name, Resource::getUrl);
291+
}
292+
293+
/**
294+
* Returns a URL for a given {@link PathVisit} or null if the argument is null.
295+
*
296+
* @param visit visited path or null
297+
* @return URL for a given path or null, in case the argument is null
298+
*/
299+
private static URL getUrl(PathVisit visit) {
300+
return visit == null ? null : visit.getUrl();
301+
}
302+
282303
@Override
283304
public byte[] getData() {
284305
lock.readLock().lock();
@@ -358,10 +379,18 @@ public boolean isDirectory() {
358379
if (pathTree.isOpen()) {
359380
return Files.isDirectory(path);
360381
}
361-
return apply(tree -> tree.apply(name, visit -> visit == null ? null : Files.isDirectory(visit.getPath())));
382+
return apply(this::isDirectory);
362383
} finally {
363384
lock.readLock().unlock();
364385
}
365386
}
387+
388+
private boolean isDirectory(OpenPathTree tree) {
389+
return tree.apply(name, Resource::isDirectory);
390+
}
391+
392+
private static boolean isDirectory(PathVisit visit) {
393+
return visit != null && Files.isDirectory(visit.getPath());
394+
}
366395
}
367396
}

0 commit comments

Comments
 (0)