|
CSR :
|
Summary
-------
`ClassLoader.definePackage` to be changed to avoid custom class loaders having to deal with `IllegalArgumentExceptions` due to concurrent calls to `definePackage()` (loading two classes in the same package concurrently).
Problem
-------
Concurrent calls to `definePackage()` can yield `IllegalArgumentException`s if the package is already defined. Some built-in class loaders, like `URLClassLoader`, already handle this case, but custom class loaders (would) all have to implement the handling of concurrent class loading.
The CDI reference implementation's default behavior (Weld) [1] and Quarkus [2] are known to run into IAEs due to concurrent calls to `definePackage()`.
Solution
--------
Change the implementation of `CL.definePackage()` to only throw an `IllegalArgumentException` if the given package properties are incompatible with the already existing one.
Properties to consider for the "compatible check" are those defined in `Package` and its super class `NamedPackage`:
* `specTitle`, `specVersion`, `specVendor`, `implTitle`, `implVersion`, `implVendor` (via `.equals()`)
* `module` (object identity comparison)
* `sealBase` via `Package.isSealed()` and `Package.isSealed(URL)`
The core implementation change in `java.lang.ClassLoader` is to functionally replace the check around `packages.putIfAbsent()` from
```
protected Package definePackage(String name, String specTitle,
implTitle, implVersion, implVendor,
sealBase, this);
...
Package p = new Package(name, specTitle, specVersion, specVendor,
implTitle, implVersion, implVendor,
sealBase, this);
if (packages.putIfAbsent(name, p) != null)
throw new IllegalArgumentException(name);
return p;
```
to
```
...
NamedPackage ex = packages.putIfAbsent(name, p);
if (ex == null)
return p;
// '.equals' here for simplicity for the CSR.
// Implementation to check the individual properties
// and use 'isSealed(URL)'.
if (ex.equals(p))
return (Package) ex;
throw new IllegalArgumentException(
"Incompatible redefinition of package " + name);
}
```
Additionally, the thrown IAE's message shall indicate the package name and a description.
The existing special handling in `BuiltinClassLoader` and `URLClassLoader` can be removed.
Specification
-------------
```
--- a/src/java.base/share/classes/java/lang/ClassLoader.java
+++ b/src/java.base/share/classes/java/lang/ClassLoader.java
@@ -2036,14 +2036,19 @@ private Package toPackage(String name, NamedPackage p, Module m) {
* respect to the given code source {@link java.net.URL URL}
* object. Otherwise, the package is not sealed.
*
- * @return The newly defined {@code Package} object
+ * @return The {@code Package} object for the given implementation
+ * and specification title/version/vendor and seal-base
+ * properties.
*
* @throws NullPointerException
* if {@code name} is {@code null}.
*
* @throws IllegalArgumentException
* if a package of the given {@code name} is already
- * defined by this class loader
+ * defined by this class loader with non-equal values for
+ * the package properties (implementation and specification
+ * version, vendor, title) or a different {@code sealBase}
+ * or module.
*
*
* @since 1.2
```
References
----------
[1] https://github.com/weld/core/blob/d55d91bec804b0c42fd8db38f74a93b99b6c541c/impl/src/main/java/org/jboss/weld/bootstrap/ConcurrentBeanDeployer.java
[2] https://github.com/quarkusio/quarkus/issues/37363