Skip to content

Commit 4ecfbf9

Browse files
authored
Merge pull request quarkusio#47899 from snazy/cl-iae-definePackage
Retry when `CL.definePackage()` throws an IAE
2 parents 698b15c + ae034dd commit 4ecfbf9

File tree

3 files changed

+91
-30
lines changed

3 files changed

+91
-30
lines changed

core/launcher/src/main/java/io/quarkus/launcher/RuntimeLaunchClassLoader.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,22 @@ protected Class<?> findClass(String name) throws ClassNotFoundException {
3535
}
3636

3737
private void definePackage(String name) {
38-
final String pkgName = getPackageNameFromClassName(name);
39-
if ((pkgName != null) && getPackage(pkgName) == null) {
40-
synchronized (getClassLoadingLock(pkgName)) {
41-
if (getPackage(pkgName) == null) {
42-
// this could certainly be improved to use the actual manifest
43-
definePackage(pkgName, null, null, null, null, null, null, null);
44-
}
38+
var pkgName = getPackageNameFromClassName(name);
39+
if (pkgName == null) {
40+
return;
41+
}
42+
if (getDefinedPackage(pkgName) != null) {
43+
return;
44+
}
45+
try {
46+
// this could certainly be improved to use the actual manifest
47+
definePackage(pkgName, null, null, null, null, null, null, null);
48+
} catch (IllegalArgumentException e) {
49+
// retry, thrown by definePackage(), if a package for the same name is already defines by this class loader.
50+
if (getDefinedPackage(pkgName) != null) {
51+
return;
4552
}
53+
throw e;
4654
}
4755
}
4856

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

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@
2727

2828
import org.jboss.logging.Logger;
2929

30+
import com.google.common.annotations.VisibleForTesting;
31+
3032
import io.quarkus.bootstrap.app.CuratedApplication;
3133
import io.quarkus.bootstrap.app.StartupAction;
3234
import io.quarkus.commons.classloading.ClassLoaderHelper;
33-
import io.quarkus.paths.ManifestAttributes;
3435
import io.quarkus.paths.PathVisit;
3536

3637
/**
@@ -141,7 +142,6 @@ public static boolean isResourcePresentAtRuntime(String resourcePath) {
141142
private final List<ClassPathElement> bannedElements;
142143
private final List<ClassPathElement> parentFirstElements;
143144
private final ConcurrentMap<ClassPathElement, ProtectionDomain> protectionDomains = new ConcurrentHashMap<>();
144-
private final ConcurrentMap<String, Package> definedPackages = new ConcurrentHashMap<>();
145145
private final ClassLoader parent;
146146
/**
147147
* If this is true it will attempt to load from the parent first
@@ -587,28 +587,33 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE
587587
}
588588
}
589589

590-
private void definePackage(String name, ClassPathElement classPathElement) {
591-
final String pkgName = getPackageNameFromClassName(name);
592-
//we can't use getPackage here
593-
//if can return a package from the parent
594-
if ((pkgName != null) && definedPackages.get(pkgName) == null) {
595-
synchronized (getClassLoadingLock(pkgName)) {
596-
if (definedPackages.get(pkgName) == null) {
597-
ManifestAttributes manifest = classPathElement.getManifestAttributes();
598-
if (manifest != null) {
599-
definedPackages.put(pkgName, definePackage(pkgName, manifest.getSpecificationTitle(),
600-
manifest.getSpecificationVersion(),
601-
manifest.getSpecificationVendor(),
602-
manifest.getImplementationTitle(),
603-
manifest.getImplementationVersion(),
604-
manifest.getImplementationVendor(), null));
605-
return;
606-
}
607-
608-
// this could certainly be improved to use the actual manifest
609-
definedPackages.put(pkgName, definePackage(pkgName, null, null, null, null, null, null, null));
610-
}
590+
@VisibleForTesting
591+
void definePackage(String name, ClassPathElement classPathElement) {
592+
var pkgName = getPackageNameFromClassName(name);
593+
if (pkgName == null) {
594+
return;
595+
}
596+
if (getDefinedPackage(pkgName) != null) {
597+
return;
598+
}
599+
try {
600+
var manifest = classPathElement.getManifestAttributes();
601+
if (manifest != null) {
602+
definePackage(pkgName, manifest.getSpecificationTitle(),
603+
manifest.getSpecificationVersion(),
604+
manifest.getSpecificationVendor(),
605+
manifest.getImplementationTitle(),
606+
manifest.getImplementationVersion(),
607+
manifest.getImplementationVendor(), null);
608+
} else {
609+
definePackage(pkgName, null, null, null, null, null, null, null);
610+
}
611+
} catch (IllegalArgumentException e) {
612+
// retry, thrown by definePackage(), if a package for the same name is already defines by this class loader.
613+
if (getDefinedPackage(pkgName) != null) {
614+
return;
611615
}
616+
throw e;
612617
}
613618
}
614619

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.quarkus.bootstrap.classloading;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import java.net.URL;
6+
import java.util.concurrent.CompletableFuture;
7+
import java.util.concurrent.CountDownLatch;
8+
import java.util.concurrent.Executors;
9+
import java.util.concurrent.TimeUnit;
10+
import java.util.stream.IntStream;
11+
12+
import org.junit.jupiter.api.Test;
13+
14+
public class ConcurrentDefinePackageTestCase {
15+
16+
/**
17+
* Validates that {@link QuarkusClassLoader} can safely do concurrent class loading against "new" packages,
18+
* backed by {@link ClassLoader#definePackage(String, String, String, String, String, String, String, URL)}, which throws an
19+
* {@link IllegalArgumentException} if the package is already defined.
20+
*/
21+
@Test
22+
public void concurrentDefinePackage() throws Exception {
23+
var threads = Math.max(8, Runtime.getRuntime().availableProcessors() * 2);
24+
var pool = Executors.newFixedThreadPool(threads);
25+
try (var quarkusClassLoader = QuarkusClassLoader.builder("test", Thread.currentThread().getContextClassLoader(), false)
26+
.build()) {
27+
for (var pkg = 0; pkg < 200; pkg++) {
28+
var readyLatch = new CountDownLatch(threads);
29+
var startLatch = new CountDownLatch(1);
30+
var pkgName = "my.package" + pkg;
31+
var futures = IntStream.range(0, threads).mapToObj(i -> CompletableFuture.runAsync(() -> {
32+
readyLatch.countDown();
33+
try {
34+
assertThat(startLatch.await(5, TimeUnit.MINUTES)).isTrue();
35+
} catch (InterruptedException e) {
36+
throw new RuntimeException(e);
37+
}
38+
quarkusClassLoader.definePackage(pkgName, ClassPathElement.EMPTY);
39+
}, pool)).toArray(CompletableFuture[]::new);
40+
assertThat(readyLatch.await(5, TimeUnit.MINUTES)).isTrue();
41+
startLatch.countDown();
42+
CompletableFuture.allOf(futures).get(5, TimeUnit.MINUTES);
43+
}
44+
} finally {
45+
pool.shutdown();
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)