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