--- old/src/java.base/share/classes/java/lang/reflect/Proxy.java 2016-07-22 14:40:49.380355493 +0200 +++ new/src/java.base/share/classes/java/lang/reflect/Proxy.java 2016-07-22 14:40:49.312355014 +0200 @@ -29,11 +29,9 @@ import java.security.PrivilegedAction; import java.util.Arrays; import java.util.Collections; -import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; @@ -49,6 +47,7 @@ import jdk.internal.misc.VM; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; +import jdk.internal.util.concurrent.ClassLoaderValue; import sun.reflect.misc.ReflectUtil; import sun.security.action.GetPropertyAction; import sun.security.util.SecurityConstants; --- old/src/java.base/share/classes/java/util/ResourceBundle.java 2016-07-22 14:40:49.595357007 +0200 +++ new/src/java.base/share/classes/java/util/ResourceBundle.java 2016-07-22 14:40:49.528356535 +0200 @@ -40,11 +40,20 @@ package java.util; +import jdk.internal.misc.JavaUtilResourceBundleAccess; +import jdk.internal.misc.SharedSecrets; +import jdk.internal.reflect.CallerSensitive; +import jdk.internal.reflect.Reflection; +import jdk.internal.util.concurrent.AbstractClassLoaderValue; +import jdk.internal.util.concurrent.ClassLoaderValue; +import sun.util.locale.BaseLocale; +import sun.util.locale.LocaleObjectCache; +import sun.util.locale.provider.ResourceBundleProviderSupport; + import java.io.IOException; import java.io.InputStream; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; -import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; @@ -56,19 +65,10 @@ import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.jar.JarEntry; import java.util.spi.ResourceBundleControlProvider; import java.util.spi.ResourceBundleProvider; -import jdk.internal.misc.JavaUtilResourceBundleAccess; -import jdk.internal.misc.SharedSecrets; -import jdk.internal.reflect.CallerSensitive; -import jdk.internal.reflect.Reflection; -import sun.util.locale.BaseLocale; -import sun.util.locale.LocaleObjectCache; -import sun.util.locale.provider.ResourceBundleProviderSupport; import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION; @@ -375,28 +375,21 @@ }); } - /** constant indicating that no resource bundle exists */ - private static final ResourceBundle NONEXISTENT_BUNDLE = new ResourceBundle() { - public Enumeration getKeys() { return null; } - protected Object handleGetObject(String key) { return null; } - public String toString() { return "NONEXISTENT_BUNDLE"; } - }; + /** + * special bundle type indicating that no resource bundle exists + */ + private static final class NONEXISTENT_BUNDLE extends ResourceBundle { + public Enumeration getKeys() { return null; } + protected Object handleGetObject(String key) { return null; } + public String toString() { return "NONEXISTENT_BUNDLE"; } + } /** - * The cache is a map from cache keys (with bundle base name, locale, and - * class loader) to either a resource bundle or NONEXISTENT_BUNDLE wrapped by a - * BundleReference. - * - * The cache is a ConcurrentMap, allowing the cache to be searched - * concurrently by multiple threads. This will also allow the cache keys - * to be reclaimed along with the ClassLoaders they reference. - * - * This variable would be better named "cache", but we keep the old - * name for compatibility with some workarounds for bug 4212439. + * The cache of BundleReference(s) per class loader, bundle base name and locale. */ - private static final ConcurrentMap cacheList - = new ConcurrentHashMap<>(INITIAL_CACHE_SIZE); + private static final ClassLoaderValue cache + = new ClassLoaderValue<>(); /** * Queue for reference objects referring to class loaders or bundles. @@ -444,10 +437,15 @@ private volatile boolean expired; /** - * The back link to the cache key. null if this bundle isn't in - * the cache (yet) or has expired. + * The time when the bundle has been loaded */ - private volatile CacheKey cacheKey; + private volatile long loadTime; + + /** + * The time when the bundle expires in the cache, or either + * Control.TTL_DONT_CACHE or Control.TTL_NO_EXPIRATION_CONTROL. + */ + private volatile long expirationTime; /** * A Set of the keys contained only in this ResourceBundle. @@ -618,76 +616,58 @@ * @param parent this bundle's parent bundle. */ protected void setParent(ResourceBundle parent) { - assert parent != NONEXISTENT_BUNDLE; + assert !(parent instanceof NONEXISTENT_BUNDLE); this.parent = parent; } /** - * Key used for cached resource bundles. The key checks the base - * name, the locale, the class loader, and the caller module - * to determine if the resource is a match to the requested one. + * A session object used during the {@link #getBundle} call. * The loader may be null, but the base name, the locale and * module must have a non-null value. */ - private static class CacheKey implements Cloneable { - // These four are the actual keys for lookup in Map. + private static class LoadSession { + // These four are the actual keys for lookup in cache. + private ClassLoader loader; + private Module module; private String name; private Locale locale; - private KeyElementReference loaderRef; - private KeyElementReference moduleRef; + // compose and return the cache key + ClassLoaderValue.Sub.Sub.Sub key() { + return cache.sub(module).sub(name).sub(locale); + } + + // return a bundle reference from cache or null + BundleReference getFromCache() { + return key().get(loader); + } // bundle format which is necessary for calling // Control.needsReload(). private String format; - // These time values are in CacheKey so that NONEXISTENT_BUNDLE - // doesn't need to be cloned for caching. - - // The time when the bundle has been loaded - private volatile long loadTime; - - // The time when the bundle expires in the cache, or either - // Control.TTL_DONT_CACHE or Control.TTL_NO_EXPIRATION_CONTROL. - private volatile long expirationTime; - // Placeholder for an error report by a Throwable private Throwable cause; - // Hash code value cache to avoid recalculating the hash code - // of this instance. - private int hashCodeCache; - // ResourceBundleProviders for loading ResourceBundles - private ServiceLoader providers; - private boolean providersChecked; + private Iterable providers; // Boolean.TRUE if the factory method caller provides a ResourceBundleProvier. private Boolean callerHasProvider; - CacheKey(String baseName, Locale locale, ClassLoader loader, Module module) { - Objects.requireNonNull(module); - - this.name = baseName; - this.locale = locale; - if (loader == null) { - this.loaderRef = null; - } else { - this.loaderRef = new KeyElementReference<>(loader, referenceQueue, this); - } - this.moduleRef = new KeyElementReference<>(module, referenceQueue, this); - calculateHashCode(); + LoadSession(String baseName, Locale locale, ClassLoader loader, Module module) { + this.name = Objects.requireNonNull(baseName); + this.locale = Objects.requireNonNull(locale); + this.loader = loader; + this.module = Objects.requireNonNull(module); } String getName() { return name; } - CacheKey setName(String baseName) { - if (!this.name.equals(baseName)) { - this.name = baseName; - calculateHashCode(); - } + LoadSession setName(String baseName) { + this.name = Objects.requireNonNull(baseName); return this; } @@ -695,118 +675,30 @@ return locale; } - CacheKey setLocale(Locale locale) { - if (!this.locale.equals(locale)) { - this.locale = locale; - calculateHashCode(); - } + LoadSession setLocale(Locale locale) { + this.locale = Objects.requireNonNull(locale); return this; } ClassLoader getLoader() { - return (loaderRef != null) ? loaderRef.get() : null; + return loader; } Module getModule() { - return moduleRef.get(); + return module; } - ServiceLoader getProviders() { - if (!providersChecked) { - providers = getServiceLoader(getModule(), name); - providersChecked = true; + Iterable getProviders() { + if (providers == null) { + providers = ResourceBundle.getProviders(module, name); } return providers; } - boolean hasProviders() { - return getProviders() != null; - } - boolean callerHasProvider() { return callerHasProvider == Boolean.TRUE; } - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - try { - final CacheKey otherEntry = (CacheKey)other; - //quick check to see if they are not equal - if (hashCodeCache != otherEntry.hashCodeCache) { - return false; - } - //are the names the same? - if (!name.equals(otherEntry.name)) { - return false; - } - // are the locales the same? - if (!locale.equals(otherEntry.locale)) { - return false; - } - //are refs (both non-null) or (both null)? - if (loaderRef == null) { - return otherEntry.loaderRef == null; - } - ClassLoader loader = getLoader(); - Module module = getModule(); - return (otherEntry.loaderRef != null) - // with a null reference we can no longer find - // out which class loader or module was referenced; so - // treat it as unequal - && (loader != null) - && (loader == otherEntry.getLoader()) - && (module != null) - && (module.equals(otherEntry.getModule())); - } catch (NullPointerException | ClassCastException e) { - } - return false; - } - - @Override - public int hashCode() { - return hashCodeCache; - } - - private void calculateHashCode() { - hashCodeCache = name.hashCode() << 3; - hashCodeCache ^= locale.hashCode(); - ClassLoader loader = getLoader(); - if (loader != null) { - hashCodeCache ^= loader.hashCode(); - } - Module module = getModule(); - if (module != null) { - hashCodeCache ^= module.hashCode(); - } - } - - @Override - public Object clone() { - try { - CacheKey clone = (CacheKey) super.clone(); - if (loaderRef != null) { - clone.loaderRef = new KeyElementReference<>(getLoader(), - referenceQueue, clone); - } - clone.moduleRef = new KeyElementReference<>(getModule(), - referenceQueue, clone); - // Clear the reference to ResourceBundleProviders and the flag - clone.providers = null; - clone.providersChecked = false; - // Clear the reference to a Throwable - clone.cause = null; - // Clear callerHasProvider - clone.callerHasProvider = null; - return clone; - } catch (CloneNotSupportedException e) { - //this should never happen - throw new InternalError(e); - } - } - String getFormat() { return format; } @@ -841,54 +733,29 @@ l = "\"\""; } } - return "CacheKey[" + name + ", lc=" + l + ", ldr=" + getLoader() + return "LookupSession[" + name + ", lc=" + l + ", ldr=" + getLoader() + "(format=" + format + ")]"; } } /** - * The common interface to get a CacheKey in LoaderReference and - * BundleReference. - */ - private static interface CacheKeyReference { - public CacheKey getCacheKey(); - } - - /** - * References to a CacheKey element as a WeakReference so that it can be - * garbage collected when nobody else is using it. - */ - private static class KeyElementReference extends WeakReference - implements CacheKeyReference { - private final CacheKey cacheKey; - - KeyElementReference(T referent, ReferenceQueue q, CacheKey key) { - super(referent, q); - cacheKey = key; - } - - @Override - public CacheKey getCacheKey() { - return cacheKey; - } - } - - /** - * References to bundles are soft references so that they can be garbage - * collected when they have no hard references. + * References to bundles are soft references so that they can be cleared + * when GC demands to free some heap. */ - private static class BundleReference extends SoftReference - implements CacheKeyReference { - private final CacheKey cacheKey; - - BundleReference(ResourceBundle referent, ReferenceQueue q, CacheKey key) { + private static class BundleReference extends SoftReference { + private final ClassLoader classLoader; + private final AbstractClassLoaderValue.Sub key; + + BundleReference(ResourceBundle referent, ReferenceQueue q, + ClassLoader classLoader, + AbstractClassLoaderValue.Sub key) { super(referent, q); - cacheKey = key; + this.classLoader = classLoader; + this.key = key; } - @Override - public CacheKey getCacheKey() { - return cacheKey; + void remove() { + key.remove(classLoader, this); } } @@ -1620,18 +1487,16 @@ throw new NullPointerException(); } - // We create a CacheKey here for use by this call. The base name + // We create a LookupSession here for use by this call. The base name // loader, and module will never change during the bundle loading - // process. We have to make sure that the locale is set before - // using it as a cache key. - CacheKey cacheKey = new CacheKey(baseName, locale, loader, module); - ResourceBundle bundle = null; + // process. + LoadSession loadSession = new LoadSession(baseName, locale, loader, module); // Quick lookup of the cache. - BundleReference bundleRef = cacheList.get(cacheKey); + ResourceBundle bundle = null; + BundleReference bundleRef = loadSession.getFromCache(); if (bundleRef != null) { bundle = bundleRef.get(); - bundleRef = null; } // If this bundle and all of its parents are valid (not expired), @@ -1661,7 +1526,7 @@ throw new IllegalArgumentException("Invalid Control: getCandidateLocales"); } - bundle = findBundle(cacheKey, module, candidateLocales, formats, 0, control, baseBundle); + bundle = findBundle(loadSession, module, candidateLocales, formats, 0, control, baseBundle); // If the loaded bundle is the base bundle and exactly for the // requested locale or the only candidate locale, then take the @@ -1687,7 +1552,7 @@ if (bundle == null) { if (baseBundle == null) { - throwMissingResourceException(baseName, locale, cacheKey.getCause()); + throwMissingResourceException(baseName, locale, loadSession.getCause()); } bundle = baseBundle; } @@ -1710,7 +1575,7 @@ return valid; } - private static ResourceBundle findBundle(CacheKey cacheKey, + private static ResourceBundle findBundle(LoadSession loadSession, Module module, List candidateLocales, List formats, @@ -1720,19 +1585,18 @@ Locale targetLocale = candidateLocales.get(index); ResourceBundle parent = null; if (index != candidateLocales.size() - 1) { - parent = findBundle(cacheKey, module, candidateLocales, formats, index + 1, + parent = findBundle(loadSession, module, candidateLocales, formats, index + 1, control, baseBundle); } else if (baseBundle != null && Locale.ROOT.equals(targetLocale)) { return baseBundle; } // Before we do the real loading work, see whether we need to - // do some housekeeping: If references to class loaders or - // resource bundles have been nulled out, remove all related - // information from the cache. + // do some housekeeping: If soft references to resource bundles + // have been nulled out, remove all related information from the cache. Object ref; while ((ref = referenceQueue.poll()) != null) { - cacheList.remove(((CacheKeyReference)ref).getCacheKey()); + ((BundleReference) ref).remove(); } // flag indicating the resource bundle has expired in the cache @@ -1740,8 +1604,8 @@ // First, look up the cache to see if it's in the cache, without // attempting to load bundle. - cacheKey.setLocale(targetLocale); - ResourceBundle bundle = findBundleInCache(cacheKey, control); + loadSession.setLocale(targetLocale); + ResourceBundle bundle = findBundleInCache(loadSession, control); if (isValidBundle(bundle)) { expiredBundle = bundle.expired; if (!expiredBundle) { @@ -1755,39 +1619,31 @@ } // Otherwise, remove the cached one since we can't keep // the same bundles having different parents. - BundleReference bundleRef = cacheList.get(cacheKey); + BundleReference bundleRef = loadSession.getFromCache(); if (bundleRef != null && bundleRef.get() == bundle) { - cacheList.remove(cacheKey, bundleRef); + bundleRef.remove(); } } } - if (bundle != NONEXISTENT_BUNDLE) { - CacheKey constKey = (CacheKey) cacheKey.clone(); - - try { - if (module.isNamed()) { - bundle = loadBundle(cacheKey, formats, control, module); - } else { - bundle = loadBundle(cacheKey, formats, control, expiredBundle); - } - if (bundle != null) { - if (bundle.parent == null) { - bundle.setParent(parent); - } - bundle.locale = targetLocale; - bundle = putBundleInCache(cacheKey, bundle, control); - return bundle; - } - - // Put NONEXISTENT_BUNDLE in the cache as a mark that there's no bundle - // instance for the locale. - putBundleInCache(cacheKey, NONEXISTENT_BUNDLE, control); - } finally { - if (constKey.getCause() instanceof InterruptedException) { - Thread.currentThread().interrupt(); + if (!(bundle instanceof NONEXISTENT_BUNDLE)) { + if (module.isNamed()) { + bundle = loadBundle(loadSession, formats, control, module); + } else { + bundle = loadBundle(loadSession, formats, control, expiredBundle); + } + if (bundle != null) { + if (bundle.parent == null) { + bundle.setParent(parent); } + bundle.locale = targetLocale; + bundle = putBundleInCache(loadSession, bundle, control); + return bundle; } + + // Put NONEXISTENT_BUNDLE in the cache as a mark that there's no bundle + // instance for the locale. + putBundleInCache(loadSession, new NONEXISTENT_BUNDLE(), control); } return parent; } @@ -1797,24 +1653,25 @@ /* * Loads a ResourceBundle in named modules */ - private static ResourceBundle loadBundle(CacheKey cacheKey, + private static ResourceBundle loadBundle(LoadSession loadSession, List formats, Control control, Module module) { - String baseName = cacheKey.getName(); - Locale targetLocale = cacheKey.getLocale(); + String baseName = loadSession.getName(); + Locale targetLocale = loadSession.getLocale(); ResourceBundle bundle = null; - if (cacheKey.hasProviders()) { - bundle = loadBundleFromProviders(baseName, targetLocale, - cacheKey.getProviders(), cacheKey); + Iterable providers = loadSession.getProviders(); + if (providers != NO_PROVIDERS) { + bundle = loadBundleFromProviders(baseName, targetLocale, + providers, loadSession); if (bundle != null) { - cacheKey.setFormat(UNKNOWN_FORMAT); + loadSession.setFormat(UNKNOWN_FORMAT); } } // If none of providers returned a bundle and the caller has no provider, // look up module-local bundles. - if (bundle == null && !cacheKey.callerHasProvider()) { + if (bundle == null && !loadSession.callerHasProvider()) { String bundleName = control.toBundleName(baseName, targetLocale); for (String format : formats) { try { @@ -1833,34 +1690,41 @@ } if (bundle != null) { - cacheKey.setFormat(format); + loadSession.setFormat(format); break; } } catch (Exception e) { - cacheKey.setCause(e); + loadSession.setCause(e); } } } return bundle; } - private static ServiceLoader getServiceLoader(Module module, - String baseName) { + // An instance that signals getting providers for unnamed module or + // inability to get providers as opposed to successfully getting 0 providers + // from a named module. + private static final Iterable NO_PROVIDERS = + Collections.emptyList(); + + private static Iterable getProviders(Module module, + String baseName) { if (!module.isNamed()) { - return null; + return NO_PROVIDERS; } PrivilegedAction pa = module::getClassLoader; ClassLoader loader = AccessController.doPrivileged(pa); - return getServiceLoader(module, loader, baseName); + return getProviders(module, loader, baseName); } - /** - * Returns a ServiceLoader that will find providers that are bound to - * a given module that may be named or unnamed. - */ - private static ServiceLoader getServiceLoader(Module module, - ClassLoader loader, - String baseName) + /** + * Returns a ServiceLoader that will find providers that are bound to + * a given module that may be named or unnamed or GET_PROVIDERS_FAILED instance + * if unsuccessful. + */ + private static Iterable getProviders(Module module, + ClassLoader loader, + String baseName) { // Look up + "Provider" String providerName = baseName + "Provider"; @@ -1887,10 +1751,9 @@ return ServiceLoader.load(service, loader, module); } catch (ServiceConfigurationError e) { // "uses" not declared: load bundle local in the module - return null; } } - return null; + return NO_PROVIDERS; } /** @@ -1898,33 +1761,30 @@ */ private static ResourceBundle loadBundleFromProviders(String baseName, Locale locale, - ServiceLoader providers, - CacheKey cacheKey) + Iterable providers, + LoadSession loadSession) { - if (providers == null) return null; - + // assert cacheKey != null && providers != null; return AccessController.doPrivileged( new PrivilegedAction<>() { public ResourceBundle run() { - for (Iterator itr = providers.iterator(); itr.hasNext(); ) { + for (ResourceBundleProvider provider : providers) { try { - ResourceBundleProvider provider = itr.next(); - if (cacheKey != null && cacheKey.callerHasProvider == null - && cacheKey.getModule() == provider.getClass().getModule()) { - cacheKey.callerHasProvider = Boolean.TRUE; + if (loadSession.callerHasProvider == null && + loadSession.getModule() == provider.getClass().getModule()) + { + loadSession.callerHasProvider = Boolean.TRUE; } ResourceBundle bundle = provider.getBundle(baseName, locale); if (bundle != null) { return bundle; } } catch (ServiceConfigurationError | SecurityException e) { - if (cacheKey != null) { - cacheKey.setCause(e); - } + loadSession.setCause(e); } } - if (cacheKey != null && cacheKey.callerHasProvider == null) { - cacheKey.callerHasProvider = Boolean.FALSE; + if (loadSession.callerHasProvider == null) { + loadSession.callerHasProvider = Boolean.FALSE; } return null; } @@ -1935,32 +1795,32 @@ /* * Legacy mechanism to load resource bundles */ - private static ResourceBundle loadBundle(CacheKey cacheKey, + private static ResourceBundle loadBundle(LoadSession loadSession, List formats, Control control, boolean reload) { // Here we actually load the bundle in the order of formats // specified by the getFormats() value. - Locale targetLocale = cacheKey.getLocale(); + Locale targetLocale = loadSession.getLocale(); ResourceBundle bundle = null; for (String format : formats) { try { // ResourceBundle.Control.newBundle may be overridden - bundle = control.newBundle(cacheKey.getName(), targetLocale, format, - cacheKey.getLoader(), reload); + bundle = control.newBundle(loadSession.getName(), targetLocale, format, + loadSession.getLoader(), reload); } catch (LinkageError | Exception error) { // We need to handle the LinkageError case due to // inconsistent case-sensitivity in ClassLoader. // See 6572242 for details. - cacheKey.setCause(error); + loadSession.setCause(error); } if (bundle != null) { // Set the format in the cache key so that it can be // used when calling needsReload later. - cacheKey.setFormat(format); - bundle.name = cacheKey.getName(); + loadSession.setFormat(format); + bundle.name = loadSession.getName(); bundle.locale = targetLocale; // Bundle provider might reuse instances. So we should make // sure to clear the expired flag here. @@ -1973,7 +1833,7 @@ } private static boolean isValidBundle(ResourceBundle bundle) { - return bundle != null && bundle != NONEXISTENT_BUNDLE; + return bundle != null && !(bundle instanceof NONEXISTENT_BUNDLE); } /** @@ -1986,12 +1846,9 @@ if (bundle.expired) { return false; } - CacheKey key = bundle.cacheKey; - if (key != null) { - long expirationTime = key.expirationTime; - if (expirationTime >= 0 && expirationTime <= now) { - return false; - } + long expirationTime = bundle.expirationTime; + if (expirationTime >= 0 && expirationTime <= now) { + return false; } bundle = bundle.parent; } @@ -2020,15 +1877,15 @@ * Finds a bundle in the cache. Any expired bundles are marked as * `expired' and removed from the cache upon return. * - * @param cacheKey the key to look up the cache + * @param loadSession the key to look up the cache * @param control the Control to be used for the expiration control * @return the cached bundle, or null if the bundle is not found in the * cache or its parent has expired. bundle.expire is true * upon return if the bundle in the cache has expired. */ - private static ResourceBundle findBundleInCache(CacheKey cacheKey, + private static ResourceBundle findBundleInCache(LoadSession loadSession, Control control) { - BundleReference bundleRef = cacheList.get(cacheKey); + BundleReference bundleRef = loadSession.getFromCache(); if (bundleRef == null) { return null; } @@ -2037,7 +1894,7 @@ return null; } ResourceBundle p = bundle.parent; - assert p != NONEXISTENT_BUNDLE; + assert !(p instanceof NONEXISTENT_BUNDLE); // If the parent has expired, then this one must also expire. We // check only the immediate parent because the actual loading is // done from the root (base) to leaf (child) and the purpose of @@ -2072,51 +1929,48 @@ // extreme case would be that getTimeToLive returns 0 and // needsReload always returns true. if (p != null && p.expired) { - assert bundle != NONEXISTENT_BUNDLE; + assert !(bundle instanceof NONEXISTENT_BUNDLE); bundle.expired = true; - bundle.cacheKey = null; - cacheList.remove(cacheKey, bundleRef); + bundleRef.remove(); bundle = null; } else { - CacheKey key = bundleRef.getCacheKey(); - long expirationTime = key.expirationTime; + long expirationTime = bundle.expirationTime; if (!bundle.expired && expirationTime >= 0 && expirationTime <= System.currentTimeMillis()) { // its TTL period has expired. - if (bundle != NONEXISTENT_BUNDLE) { + if (!(bundle instanceof NONEXISTENT_BUNDLE)) { // Synchronize here to call needsReload to avoid // redundant concurrent calls for the same bundle. synchronized (bundle) { - expirationTime = key.expirationTime; + expirationTime = bundle.expirationTime; if (!bundle.expired && expirationTime >= 0 && expirationTime <= System.currentTimeMillis()) { try { - bundle.expired = control.needsReload(key.getName(), - key.getLocale(), - key.getFormat(), - key.getLoader(), + bundle.expired = control.needsReload(loadSession.getName(), + loadSession.getLocale(), + loadSession.getFormat(), + loadSession.getLoader(), bundle, - key.loadTime); + bundle.loadTime); } catch (Exception e) { - cacheKey.setCause(e); + loadSession.setCause(e); } if (bundle.expired) { // If the bundle needs to be reloaded, then // remove the bundle from the cache, but // return the bundle with the expired flag // on. - bundle.cacheKey = null; - cacheList.remove(cacheKey, bundleRef); + bundleRef.remove(); } else { // Update the expiration control info. and reuse // the same bundle instance - setExpirationTime(key, control); + setExpirationTime(bundle, loadSession, control); } } } } else { // We just remove NONEXISTENT_BUNDLE from the cache. - cacheList.remove(cacheKey, bundleRef); + bundleRef.remove(); bundle = null; } } @@ -2127,56 +1981,61 @@ /** * Put a new bundle in the cache. * - * @param cacheKey the key for the resource bundle + * @param loadSession the key for the resource bundle * @param bundle the resource bundle to be put in the cache * @return the ResourceBundle for the cacheKey; if someone has put * the bundle before this call, the one found in the cache is * returned. */ - private static ResourceBundle putBundleInCache(CacheKey cacheKey, + private static ResourceBundle putBundleInCache(LoadSession loadSession, ResourceBundle bundle, Control control) { - setExpirationTime(cacheKey, control); - if (cacheKey.expirationTime != Control.TTL_DONT_CACHE) { - CacheKey key = (CacheKey) cacheKey.clone(); - BundleReference bundleRef = new BundleReference(bundle, referenceQueue, key); - bundle.cacheKey = key; + setExpirationTime(bundle, loadSession, control); + if (bundle.expirationTime != Control.TTL_DONT_CACHE) { + ClassLoaderValue.Sub.Sub.Sub key + = loadSession.key(); + ClassLoader loader = loadSession.getLoader(); + BundleReference bundleRef = new BundleReference( + bundle, referenceQueue, loader, key); // Put the bundle in the cache if it's not been in the cache. - BundleReference result = cacheList.putIfAbsent(key, bundleRef); + BundleReference oldBundleRef = key.putIfAbsent(loader, bundleRef); // If someone else has put the same bundle in the cache before // us and it has not expired, we should use the one in the cache. - if (result != null) { - ResourceBundle rb = result.get(); + while (oldBundleRef != null) { + ResourceBundle rb = oldBundleRef.get(); if (rb != null && !rb.expired) { - // Clear the back link to the cache key - bundle.cacheKey = null; - bundle = rb; - // Clear the reference in the BundleReference so that - // it won't be enqueued. - bundleRef.clear(); + return rb; } else { - // Replace the invalid (garbage collected or expired) + // Try to replace the invalid (garbage collected or expired) // instance with the valid one. - cacheList.put(key, bundleRef); + if (key.replace(loader, oldBundleRef, bundleRef)) { + break; + } else { + // Someone else must have already replaced it or it was + // removed. Retry putting the bundle in the cache. + oldBundleRef = key.putIfAbsent(loader, bundleRef); + } } } } return bundle; } - private static void setExpirationTime(CacheKey cacheKey, Control control) { - long ttl = control.getTimeToLive(cacheKey.getName(), - cacheKey.getLocale()); + private static void setExpirationTime(ResourceBundle bundle, + LoadSession loadSession, + Control control) { + long ttl = control.getTimeToLive(loadSession.getName(), + loadSession.getLocale()); if (ttl >= 0) { // If any expiration time is specified, set the time to be // expired in the cache. long now = System.currentTimeMillis(); - cacheKey.loadTime = now; - cacheKey.expirationTime = now + ttl; + bundle.loadTime = now; + bundle.expirationTime = now + ttl; } else if (ttl >= Control.TTL_NO_EXPIRATION_CONTROL) { - cacheKey.expirationTime = ttl; + bundle.expirationTime = ttl; } else { throw new IllegalArgumentException("Invalid Control: TTL=" + ttl); } @@ -2229,9 +2088,7 @@ } private static void clearCache(ClassLoader loader, Module module) { - Set set = cacheList.keySet(); - set.stream().filter((key) -> (key.getLoader() == loader && key.getModule() == module)) - .forEach(set::remove); + cache.sub(module).removeAll(loader); } /** --- old/test/java/util/ResourceBundle/ReferencesTest.java 2016-07-22 14:40:49.859358866 +0200 +++ new/test/java/util/ResourceBundle/ReferencesTest.java 2016-07-22 14:40:49.793358401 +0200 @@ -105,15 +105,9 @@ int first = countLoaders(0, CLASS_LOADER_COUNT / 2); int second = countLoaders(CLASS_LOADER_COUNT / 2, CLASS_LOADER_COUNT); - Class clazz = ResourceBundle.class; - Field cacheList = clazz.getDeclaredField("cacheList"); - cacheList.setAccessible(true); - int cacheSize = ((Map)cacheList.get(clazz)).size(); - System.out.println(when); System.out.println(" " + first + " loaders alive in first half"); System.out.println(" " + second + " loaders alive in second half"); - System.out.println(" " + cacheSize + " entries in resource bundle cache"); } private static void loadBundles(int start, int end) throws Exception { --- old/src/java.base/share/classes/java/lang/reflect/AbstractClassLoaderValue.java 2016-07-22 14:40:50.092360507 +0200 +++ /dev/null 2016-07-18 09:58:05.363251509 +0200 @@ -1,431 +0,0 @@ -/* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package java.lang.reflect; - -import jdk.internal.loader.BootLoader; -import jdk.internal.misc.JavaLangAccess; -import jdk.internal.misc.SharedSecrets; - -import java.util.Iterator; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BiFunction; -import java.util.function.Supplier; - -/** - * AbstractClassLoaderValue is a superclass of root-{@link ClassLoaderValue} - * and {@link Sub sub}-ClassLoaderValue. - * - * @param the type of concrete ClassLoaderValue (this type) - * @param the type of values associated with ClassLoaderValue - */ -abstract class AbstractClassLoaderValue, V> { - - /** - * Sole constructor. - */ - AbstractClassLoaderValue() {} - - /** - * Returns the key component of this ClassLoaderValue. The key component of - * the root-{@link ClassLoaderValue} is the ClassLoaderValue itself, - * while the key component of a {@link #sub(Object) sub}-ClassLoaderValue - * is what was given to construct it. - * - * @return the key component of this ClassLoaderValue. - */ - public abstract Object key(); - - /** - * Constructs new sub-ClassLoaderValue of this ClassLoaderValue with given - * key component. - * - * @param key the key component of the sub-ClassLoaderValue. - * @param the type of the key component. - * @return a sub-ClassLoaderValue of this ClassLoaderValue for given key - */ - public Sub sub(K key) { - return new Sub<>(key); - } - - /** - * Returns {@code true} if this ClassLoaderValue is equal to given {@code clv} - * or if this ClassLoaderValue was derived from given {@code clv} by a chain - * of {@link #sub(Object)} invocations. - * - * @param clv the ClassLoaderValue to test this against - * @return if this ClassLoaderValue is equal to given {@code clv} or - * its descendant - */ - public abstract boolean isEqualOrDescendantOf(AbstractClassLoaderValue clv); - - /** - * Returns the value associated with this ClassLoaderValue and given ClassLoader - * or {@code null} if there is none. - * - * @param cl the ClassLoader for the associated value - * @return the value associated with this ClassLoaderValue and given ClassLoader - * or {@code null} if there is none. - */ - public V get(ClassLoader cl) { - Object val = AbstractClassLoaderValue.map(cl).get(this); - try { - return extractValue(val); - } catch (Memoizer.RecursiveInvocationException e) { - // propagate recursive get() for the same key that is just - // being calculated in computeIfAbsent() - throw e; - } catch (Throwable t) { - // don't propagate exceptions thrown from Memoizer - pretend - // that there was no entry - // (computeIfAbsent invocation will try to remove it anyway) - return null; - } - } - - /** - * Associates given value {@code v} with this ClassLoaderValue and given - * ClassLoader and returns {@code null} if there was no previously associated - * value or does nothing and returns previously associated value if there - * was one. - * - * @param cl the ClassLoader for the associated value - * @param v the value to associate - * @return previously associated value or null if there was none - */ - public V putIfAbsent(ClassLoader cl, V v) { - ConcurrentHashMap map = map(cl); - @SuppressWarnings("unchecked") - CLV clv = (CLV) this; - while (true) { - try { - Object val = map.putIfAbsent(clv, v); - return extractValue(val); - } catch (Memoizer.RecursiveInvocationException e) { - // propagate RecursiveInvocationException for the same key that - // is just being calculated in computeIfAbsent - throw e; - } catch (Throwable t) { - // don't propagate exceptions thrown from foreign Memoizer - - // pretend that there was no entry and retry - // (foreign computeIfAbsent invocation will try to remove it anyway) - } - // TODO: - // Thread.onSpinLoop(); // when available - } - } - - /** - * Removes the value associated with this ClassLoaderValue and given - * ClassLoader if the associated value is equal to given value {@code v} and - * returns {@code true} or does nothing and returns {@code false} if there is - * no currently associated value or it is not equal to given value {@code v}. - * - * @param cl the ClassLoader for the associated value - * @param v the value to compare with currently associated value - * @return {@code true} if the association was removed or {@code false} if not - */ - public boolean remove(ClassLoader cl, Object v) { - return AbstractClassLoaderValue.map(cl).remove(this, v); - } - - /** - * Returns the value associated with this ClassLoaderValue and given - * ClassLoader if there is one or computes the value by invoking given - * {@code mappingFunction}, associates it and returns it. - *

- * Computation and association of the computed value is performed atomically - * by the 1st thread that requests a particular association while holding a - * lock associated with this ClassLoaderValue and given ClassLoader. - * Nested calls from the {@code mappingFunction} to {@link #get}, - * {@link #putIfAbsent} or {@link #computeIfAbsent} for the same association - * are not allowed and throw {@link IllegalStateException}. Nested call to - * {@link #remove} for the same association is allowed but will always return - * {@code false} regardless of passed-in comparison value. Nested calls for - * other association(s) are allowed, but care should be taken to avoid - * deadlocks. When two threads perform nested computations of the overlapping - * set of associations they should always request them in the same order. - * - * @param cl the ClassLoader for the associated value - * @param mappingFunction the function to compute the value - * @return the value associated with this ClassLoaderValue and given - * ClassLoader. - * @throws IllegalStateException if a direct or indirect invocation from - * within given {@code mappingFunction} that - * computes the value of a particular association - * to {@link #get}, {@link #putIfAbsent} or - * {@link #computeIfAbsent} - * for the same association is attempted. - */ - public V computeIfAbsent(ClassLoader cl, - BiFunction< - ? super ClassLoader, - ? super CLV, - ? extends V - > mappingFunction) throws IllegalStateException { - ConcurrentHashMap map = map(cl); - @SuppressWarnings("unchecked") - CLV clv = (CLV) this; - Memoizer mv = null; - while (true) { - Object val = (mv == null) ? map.get(clv) : map.putIfAbsent(clv, mv); - if (val == null) { - if (mv == null) { - // create Memoizer lazily when 1st needed and restart loop - mv = new Memoizer<>(cl, clv, mappingFunction); - continue; - } - // mv != null, therefore sv == null was a result of successful - // putIfAbsent - try { - // trigger Memoizer to compute the value - V v = mv.get(); - // attempt to replace our Memoizer with the value - map.replace(clv, mv, v); - // return computed value - return v; - } catch (Throwable t) { - // our Memoizer has thrown, attempt to remove it - map.remove(clv, mv); - // propagate exception because it's from our Memoizer - throw t; - } - } else { - try { - return extractValue(val); - } catch (Memoizer.RecursiveInvocationException e) { - // propagate recursive attempts to calculate the same - // value as being calculated at the moment - throw e; - } catch (Throwable t) { - // don't propagate exceptions thrown from foreign Memoizer - - // pretend that there was no entry and retry - // (foreign computeIfAbsent invocation will try to remove it anyway) - } - } - // TODO: - // Thread.onSpinLoop(); // when available - } - } - - /** - * Removes all values associated with given ClassLoader {@code cl} and - * {@link #isEqualOrDescendantOf(AbstractClassLoaderValue) this or descendants} - * of this ClassLoaderValue. - * This is not an atomic operation. Other threads may see some associations - * be already removed and others still present while this method is executing. - *

- * The sole intention of this method is to cleanup after a unit test that - * tests ClassLoaderValue directly. It is not intended for use in - * actual algorithms. - * - * @param cl the associated ClassLoader of the values to be removed - */ - public void removeAll(ClassLoader cl) { - ConcurrentHashMap map = map(cl); - for (Iterator i = map.keySet().iterator(); i.hasNext(); ) { - if (i.next().isEqualOrDescendantOf(this)) { - i.remove(); - } - } - } - - private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); - - /** - * @return a ConcurrentHashMap for given ClassLoader - */ - @SuppressWarnings("unchecked") - private static > - ConcurrentHashMap map(ClassLoader cl) { - return (ConcurrentHashMap) - (cl == null ? BootLoader.getClassLoaderValueMap() - : JLA.createOrGetClassLoaderValueMap(cl)); - } - - /** - * @return value extracted from the {@link Memoizer} if given - * {@code memoizerOrValue} parameter is a {@code Memoizer} or - * just return given parameter. - */ - @SuppressWarnings("unchecked") - private V extractValue(Object memoizerOrValue) { - if (memoizerOrValue instanceof Memoizer) { - return ((Memoizer) memoizerOrValue).get(); - } else { - return (V) memoizerOrValue; - } - } - - /** - * A memoized supplier that invokes given {@code mappingFunction} just once - * and remembers the result or thrown exception for subsequent calls. - * If given mappingFunction returns null, it is converted to NullPointerException, - * thrown from the Memoizer's {@link #get()} method and remembered. - * If the Memoizer is invoked recursively from the given {@code mappingFunction}, - * {@link RecursiveInvocationException} is thrown, but it is not remembered. - * The in-flight call to the {@link #get()} can still complete successfully if - * such exception is handled by the mappingFunction. - */ - private static final class Memoizer, V> - implements Supplier { - - private final ClassLoader cl; - private final CLV clv; - private final BiFunction - mappingFunction; - - private volatile V v; - private volatile Throwable t; - private boolean inCall; - - Memoizer(ClassLoader cl, - CLV clv, - BiFunction - mappingFunction - ) { - this.cl = cl; - this.clv = clv; - this.mappingFunction = mappingFunction; - } - - @Override - public V get() throws RecursiveInvocationException { - V v = this.v; - if (v != null) return v; - Throwable t = this.t; - if (t == null) { - synchronized (this) { - if ((v = this.v) == null && (t = this.t) == null) { - if (inCall) { - throw new RecursiveInvocationException(); - } - inCall = true; - try { - this.v = v = Objects.requireNonNull( - mappingFunction.apply(cl, clv)); - } catch (Throwable x) { - this.t = t = x; - } finally { - inCall = false; - } - } - } - } - if (v != null) return v; - if (t instanceof Error) { - throw (Error) t; - } else if (t instanceof RuntimeException) { - throw (RuntimeException) t; - } else { - throw new UndeclaredThrowableException(t); - } - } - - static class RecursiveInvocationException extends IllegalStateException { - private static final long serialVersionUID = 1L; - - RecursiveInvocationException() { - super("Recursive call"); - } - } - } - - /** - * sub-ClassLoaderValue is an inner class of {@link AbstractClassLoaderValue} - * and also a subclass of it. It can therefore be instantiated as an inner - * class of either an instance of root-{@link ClassLoaderValue} or another - * instance of itself. This enables composing type-safe compound keys of - * arbitrary length: - *

{@code
-     * ClassLoaderValue clv = new ClassLoaderValue<>();
-     * ClassLoaderValue.Sub.Sub.Sub clv_k123 =
-     *     clv.sub(k1).sub(k2).sub(k3);
-     * }
- * From which individual components are accessible in a type-safe way: - *
{@code
-     * K1 k1 = clv_k123.parent().parent().key();
-     * K2 k2 = clv_k123.parent().key();
-     * K3 k3 = clv_k123.key();
-     * }
- * This allows specifying non-capturing lambdas for the mapping function of - * {@link #computeIfAbsent(ClassLoader, BiFunction)} operation that can - * access individual key components from passed-in - * sub-[sub-...]ClassLoaderValue instance in a type-safe way. - * - * @param the type of {@link #key()} component contained in the - * sub-ClassLoaderValue. - */ - final class Sub extends AbstractClassLoaderValue, V> { - - private final K key; - - Sub(K key) { - this.key = key; - } - - /** - * @return the parent ClassLoaderValue this sub-ClassLoaderValue - * has been {@link #sub(Object) derived} from. - */ - public AbstractClassLoaderValue parent() { - return AbstractClassLoaderValue.this; - } - - /** - * @return the key component of this sub-ClassLoaderValue. - */ - @Override - public K key() { - return key; - } - - /** - * sub-ClassLoaderValue is a descendant of given {@code clv} if it is - * either equal to it or if its {@link #parent() parent} is a - * descendant of given {@code clv}. - */ - @Override - public boolean isEqualOrDescendantOf(AbstractClassLoaderValue clv) { - return equals(Objects.requireNonNull(clv)) || - parent().isEqualOrDescendantOf(clv); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof Sub)) return false; - @SuppressWarnings("unchecked") - Sub that = (Sub) o; - return this.parent().equals(that.parent()) && - Objects.equals(this.key, that.key); - } - - @Override - public int hashCode() { - return 31 * parent().hashCode() + - Objects.hashCode(key); - } - } -} --- /dev/null 2016-07-18 09:58:05.363251509 +0200 +++ new/src/java.base/share/classes/jdk/internal/util/concurrent/AbstractClassLoaderValue.java 2016-07-22 14:40:49.994359817 +0200 @@ -0,0 +1,450 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.util.concurrent; + +import jdk.internal.loader.BootLoader; +import jdk.internal.misc.JavaLangAccess; +import jdk.internal.misc.SharedSecrets; + +import java.lang.reflect.UndeclaredThrowableException; +import java.util.Iterator; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +/** + * AbstractClassLoaderValue is a superclass of root-{@link ClassLoaderValue} + * and {@link Sub sub}-ClassLoaderValue. + * + * @param the type of concrete ClassLoaderValue (this type) + * @param the type of values associated with ClassLoaderValue + */ +public abstract class AbstractClassLoaderValue, V> { + + /** + * Sole constructor. + */ + AbstractClassLoaderValue() {} + + /** + * Returns the key component of this ClassLoaderValue. The key component of + * the root-{@link ClassLoaderValue} is the ClassLoaderValue itself, + * while the key component of a {@link #sub(Object) sub}-ClassLoaderValue + * is what was given to construct it. + * + * @return the key component of this ClassLoaderValue. + */ + public abstract Object key(); + + /** + * Constructs new sub-ClassLoaderValue of this ClassLoaderValue with given + * key component. + * + * @param key the key component of the sub-ClassLoaderValue. + * @param the type of the key component. + * @return a sub-ClassLoaderValue of this ClassLoaderValue for given key + */ + public Sub sub(K key) { + return new Sub<>(key); + } + + /** + * Returns {@code true} if this ClassLoaderValue is equal to given {@code clv} + * or if this ClassLoaderValue was derived from given {@code clv} by a chain + * of {@link #sub(Object)} invocations. + * + * @param clv the ClassLoaderValue to test this against + * @return if this ClassLoaderValue is equal to given {@code clv} or + * its descendant + */ + public abstract boolean isEqualOrDescendantOf(AbstractClassLoaderValue clv); + + /** + * Returns the value associated with this ClassLoaderValue and given ClassLoader + * or {@code null} if there is none. + * + * @param cl the ClassLoader for the associated value + * @return the value associated with this ClassLoaderValue and given ClassLoader + * or {@code null} if there is none. + */ + public V get(ClassLoader cl) { + Object val = AbstractClassLoaderValue.map(cl).get(this); + try { + return extractValue(val); + } catch (Memoizer.RecursiveInvocationException e) { + // propagate recursive get() for the same key that is just + // being calculated in computeIfAbsent() + throw e; + } catch (Throwable t) { + // don't propagate exceptions thrown from Memoizer - pretend + // that there was no entry + // (computeIfAbsent invocation will try to remove it anyway) + return null; + } + } + + /** + * Associates given value {@code v} with this ClassLoaderValue and given + * ClassLoader and returns {@code null} if there was no previously associated + * value or does nothing and returns previously associated value if there + * was one. + * + * @param cl the ClassLoader for the associated value + * @param v the value to associate + * @return previously associated value or null if there was none + */ + public V putIfAbsent(ClassLoader cl, V v) { + ConcurrentHashMap map = map(cl); + @SuppressWarnings("unchecked") + CLV clv = (CLV) this; + while (true) { + try { + Object val = map.putIfAbsent(clv, v); + return extractValue(val); + } catch (Memoizer.RecursiveInvocationException e) { + // propagate RecursiveInvocationException for the same key that + // is just being calculated in computeIfAbsent + throw e; + } catch (Throwable t) { + // don't propagate exceptions thrown from foreign Memoizer - + // pretend that there was no entry and retry + // (foreign computeIfAbsent invocation will try to remove it anyway) + } + // TODO: + // Thread.onSpinLoop(); // when available + } + } + + /** + * Removes the value associated with this ClassLoaderValue and given + * ClassLoader if the associated value is equal to given value {@code v} and + * returns {@code true} or does nothing and returns {@code false} if there is + * no currently associated value or it is not equal to given value {@code v}. + * + * @param cl the ClassLoader for the associated value + * @param v the value to compare with currently associated value + * @return {@code true} if the association was removed or {@code false} if not + */ + public boolean remove(ClassLoader cl, Object v) { + return AbstractClassLoaderValue.map(cl).remove(this, v); + } + + /** + * Replaces the value associated with this ClassLoaderValue and given + * ClassLoader with {@code newV} if the associated value is equal to given + * value {@code oldV} and returns {@code true} or does nothing and returns + * {@code false} if there is no currently associated value or it is not equal + * to given value {@code oldV}. + * + * @param cl the ClassLoader for the associated value + * @param oldV the value to compare with currently associated value + * @param newV the value to associate if current value is equal to oldV + * @return {@code true} if the association was replaced or {@code false} if not + */ + public boolean replace(ClassLoader cl, V oldV, V newV) { + @SuppressWarnings("unchecked") + CLV clv = (CLV) this; + return AbstractClassLoaderValue.map(cl).replace(clv, oldV, newV); + } + + /** + * Returns the value associated with this ClassLoaderValue and given + * ClassLoader if there is one or computes the value by invoking given + * {@code mappingFunction}, associates it and returns it. + *

+ * Computation and association of the computed value is performed atomically + * by the 1st thread that requests a particular association while holding a + * lock associated with this ClassLoaderValue and given ClassLoader. + * Nested calls from the {@code mappingFunction} to {@link #get}, + * {@link #putIfAbsent} or {@link #computeIfAbsent} for the same association + * are not allowed and throw {@link IllegalStateException}. Nested call to + * {@link #remove} for the same association is allowed but will always return + * {@code false} regardless of passed-in comparison value. Nested calls for + * other association(s) are allowed, but care should be taken to avoid + * deadlocks. When two threads perform nested computations of the overlapping + * set of associations they should always request them in the same order. + * + * @param cl the ClassLoader for the associated value + * @param mappingFunction the function to compute the value + * @return the value associated with this ClassLoaderValue and given + * ClassLoader. + * @throws IllegalStateException if a direct or indirect invocation from + * within given {@code mappingFunction} that + * computes the value of a particular association + * to {@link #get}, {@link #putIfAbsent} or + * {@link #computeIfAbsent} + * for the same association is attempted. + */ + public V computeIfAbsent(ClassLoader cl, + BiFunction< + ? super ClassLoader, + ? super CLV, + ? extends V + > mappingFunction) throws IllegalStateException { + ConcurrentHashMap map = map(cl); + @SuppressWarnings("unchecked") + CLV clv = (CLV) this; + Memoizer mv = null; + while (true) { + Object val = (mv == null) ? map.get(clv) : map.putIfAbsent(clv, mv); + if (val == null) { + if (mv == null) { + // create Memoizer lazily when 1st needed and restart loop + mv = new Memoizer<>(cl, clv, mappingFunction); + continue; + } + // mv != null, therefore sv == null was a result of successful + // putIfAbsent + try { + // trigger Memoizer to compute the value + V v = mv.get(); + // attempt to replace our Memoizer with the value + map.replace(clv, mv, v); + // return computed value + return v; + } catch (Throwable t) { + // our Memoizer has thrown, attempt to remove it + map.remove(clv, mv); + // propagate exception because it's from our Memoizer + throw t; + } + } else { + try { + return extractValue(val); + } catch (Memoizer.RecursiveInvocationException e) { + // propagate recursive attempts to calculate the same + // value as being calculated at the moment + throw e; + } catch (Throwable t) { + // don't propagate exceptions thrown from foreign Memoizer - + // pretend that there was no entry and retry + // (foreign computeIfAbsent invocation will try to remove it anyway) + } + } + // TODO: + // Thread.onSpinLoop(); // when available + } + } + + /** + * Removes all values associated with given ClassLoader {@code cl} and + * {@link #isEqualOrDescendantOf(AbstractClassLoaderValue) this or descendants} + * of this ClassLoaderValue. + * This is not an atomic operation. Other threads may see some associations + * be already removed and others still present while this method is executing. + *

+ * The sole intention of this method is to cleanup after a unit test that + * tests ClassLoaderValue directly. It is not intended for use in + * actual algorithms. + * + * @param cl the associated ClassLoader of the values to be removed + */ + public void removeAll(ClassLoader cl) { + ConcurrentHashMap map = map(cl); + for (Iterator i = map.keySet().iterator(); i.hasNext(); ) { + if (i.next().isEqualOrDescendantOf(this)) { + i.remove(); + } + } + } + + private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + + /** + * @return a ConcurrentHashMap for given ClassLoader + */ + @SuppressWarnings("unchecked") + private static > + ConcurrentHashMap map(ClassLoader cl) { + return (ConcurrentHashMap) + (cl == null ? BootLoader.getClassLoaderValueMap() + : JLA.createOrGetClassLoaderValueMap(cl)); + } + + /** + * @return value extracted from the {@link Memoizer} if given + * {@code memoizerOrValue} parameter is a {@code Memoizer} or + * just return given parameter. + */ + @SuppressWarnings("unchecked") + private V extractValue(Object memoizerOrValue) { + if (memoizerOrValue instanceof Memoizer) { + return ((Memoizer) memoizerOrValue).get(); + } else { + return (V) memoizerOrValue; + } + } + + /** + * A memoized supplier that invokes given {@code mappingFunction} just once + * and remembers the result or thrown exception for subsequent calls. + * If given mappingFunction returns null, it is converted to NullPointerException, + * thrown from the Memoizer's {@link #get()} method and remembered. + * If the Memoizer is invoked recursively from the given {@code mappingFunction}, + * {@link RecursiveInvocationException} is thrown, but it is not remembered. + * The in-flight call to the {@link #get()} can still complete successfully if + * such exception is handled by the mappingFunction. + */ + private static final class Memoizer, V> + implements Supplier { + + private final ClassLoader cl; + private final CLV clv; + private final BiFunction + mappingFunction; + + private volatile V v; + private volatile Throwable t; + private boolean inCall; + + Memoizer(ClassLoader cl, + CLV clv, + BiFunction + mappingFunction + ) { + this.cl = cl; + this.clv = clv; + this.mappingFunction = mappingFunction; + } + + @Override + public V get() throws RecursiveInvocationException { + V v = this.v; + if (v != null) return v; + Throwable t = this.t; + if (t == null) { + synchronized (this) { + if ((v = this.v) == null && (t = this.t) == null) { + if (inCall) { + throw new RecursiveInvocationException(); + } + inCall = true; + try { + this.v = v = Objects.requireNonNull( + mappingFunction.apply(cl, clv)); + } catch (Throwable x) { + this.t = t = x; + } finally { + inCall = false; + } + } + } + } + if (v != null) return v; + if (t instanceof Error) { + throw (Error) t; + } else if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } else { + throw new UndeclaredThrowableException(t); + } + } + + static class RecursiveInvocationException extends IllegalStateException { + private static final long serialVersionUID = 1L; + + RecursiveInvocationException() { + super("Recursive call"); + } + } + } + + /** + * sub-ClassLoaderValue is an inner class of {@link AbstractClassLoaderValue} + * and also a subclass of it. It can therefore be instantiated as an inner + * class of either an instance of root-{@link ClassLoaderValue} or another + * instance of itself. This enables composing type-safe compound keys of + * arbitrary length: + *

{@code
+     * ClassLoaderValue clv = new ClassLoaderValue<>();
+     * ClassLoaderValue.Sub.Sub.Sub clv_k123 =
+     *     clv.sub(k1).sub(k2).sub(k3);
+     * }
+ * From which individual components are accessible in a type-safe way: + *
{@code
+     * K1 k1 = clv_k123.parent().parent().key();
+     * K2 k2 = clv_k123.parent().key();
+     * K3 k3 = clv_k123.key();
+     * }
+ * This allows specifying non-capturing lambdas for the mapping function of + * {@link #computeIfAbsent(ClassLoader, BiFunction)} operation that can + * access individual key components from passed-in + * sub-[sub-...]ClassLoaderValue instance in a type-safe way. + * + * @param the type of {@link #key()} component contained in the + * sub-ClassLoaderValue. + */ + public final class Sub extends AbstractClassLoaderValue, V> { + + private final K key; + + Sub(K key) { + this.key = key; + } + + /** + * @return the parent ClassLoaderValue this sub-ClassLoaderValue + * has been {@link #sub(Object) derived} from. + */ + public AbstractClassLoaderValue parent() { + return AbstractClassLoaderValue.this; + } + + /** + * @return the key component of this sub-ClassLoaderValue. + */ + @Override + public K key() { + return key; + } + + /** + * sub-ClassLoaderValue is a descendant of given {@code clv} if it is + * either equal to it or if its {@link #parent() parent} is a + * descendant of given {@code clv}. + */ + @Override + public boolean isEqualOrDescendantOf(AbstractClassLoaderValue clv) { + return equals(Objects.requireNonNull(clv)) || + parent().isEqualOrDescendantOf(clv); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Sub)) return false; + @SuppressWarnings("unchecked") + Sub that = (Sub) o; + return this.parent().equals(that.parent()) && + Objects.equals(this.key, that.key); + } + + @Override + public int hashCode() { + return 31 * parent().hashCode() + + Objects.hashCode(key); + } + } +} --- old/src/java.base/share/classes/java/lang/reflect/ClassLoaderValue.java 2016-07-22 14:40:50.335362219 +0200 +++ /dev/null 2016-07-18 09:58:05.363251509 +0200 @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package java.lang.reflect; - -import java.util.Objects; -import java.util.function.BiFunction; - -/** - * root-ClassLoaderValue. Each instance defines a separate namespace for - * associated values. - *

- * ClassLoaderValue allows associating a - * {@link #computeIfAbsent(ClassLoader, BiFunction) computed} non-null value with - * a {@code (ClassLoader, keys...)} tuple. The associated value, as well as the - * keys are strongly reachable from the associated ClassLoader so care should be - * taken to use such keys and values that only reference types resolvable from - * the associated ClassLoader. Failing that, ClassLoader leaks are inevitable. - *

- * Example usage: - *

{@code
- * // create a root instance which represents a namespace and declares the type of
- * // associated values (Class instances in this example)
- * static final ClassLoaderValue> proxyClasses = new ClassLoaderValue<>();
- *
- * // create a compound key composed of a Module and a list of interfaces
- * Module module = ...;
- * List> interfaces = ...;
- * ClassLoaderValue>.Sub.Sub>> key =
- *     proxyClasses.sub(module).sub(interfaces);
- *
- * // use the compound key together with ClassLoader to lazily associate
- * // the value with tuple (loader, module, interfaces) and return it
- * ClassLoader loader = ...;
- * Class proxyClass = key.computeIfAbsent(loader, (ld, ky) -> {
- *     List> intfcs = ky.key();
- *     Module m = ky.parent().key();
- *     Class clazz = defineProxyClass(ld, m, intfcs);
- *     return clazz;
- * });
- * }
- *

- * {@code classLoaderValue.(classLoader, ...)} represents an operation - * to {@link #get}, {@link #putIfAbsent}, {@link #computeIfAbsent} or {@link #remove} - * a value associated with a (classLoader, classLoaderValue) tuple. ClassLoader - * instances and root-{@link ClassLoaderValue} instances are compared using - * identity equality while {@link Sub sub}-ClassLoaderValue instances define - * {@link #equals(Object) equality} in terms of equality of its - * {@link Sub#parent() parent} ClassLoaderValue and its - * {@link #key() key} component. - * - * @param the type of value(s) associated with the root-ClassLoaderValue and - * all its {@link #sub(Object) descendants}. - * @author Peter Levart - * @since 9 - */ -final class ClassLoaderValue - extends AbstractClassLoaderValue, V> { - - /** - * Constructs new root-ClassLoaderValue representing its own namespace. - */ - public ClassLoaderValue() {} - - /** - * @return the key component of this root-ClassLoaderValue (itself). - */ - @Override - public ClassLoaderValue key() { - return this; - } - - /** - * root-ClassLoaderValue can only be equal to itself and has no predecessors. - */ - @Override - public boolean isEqualOrDescendantOf(AbstractClassLoaderValue clv) { - return equals(Objects.requireNonNull(clv)); - } -} --- /dev/null 2016-07-18 09:58:05.363251509 +0200 +++ new/src/java.base/share/classes/jdk/internal/util/concurrent/ClassLoaderValue.java 2016-07-22 14:40:50.234361507 +0200 @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.util.concurrent; + +import java.util.Objects; +import java.util.function.BiFunction; + +/** + * root-ClassLoaderValue. Each instance defines a separate namespace for + * associated values. + *

+ * ClassLoaderValue allows associating a + * {@link #computeIfAbsent(ClassLoader, BiFunction) computed} non-null value with + * a {@code (ClassLoader, keys...)} tuple. The associated value, as well as the + * keys are strongly reachable from the associated ClassLoader so care should be + * taken to use such keys and values that only reference types resolvable from + * the associated ClassLoader. Failing that, ClassLoader leaks are inevitable. + *

+ * Example usage: + *

{@code
+ * // create a root instance which represents a namespace and declares the type of
+ * // associated values (Class instances in this example)
+ * static final ClassLoaderValue> proxyClasses = new ClassLoaderValue<>();
+ *
+ * // create a compound key composed of a Module and a list of interfaces
+ * Module module = ...;
+ * List> interfaces = ...;
+ * ClassLoaderValue>.Sub.Sub>> key =
+ *     proxyClasses.sub(module).sub(interfaces);
+ *
+ * // use the compound key together with ClassLoader to lazily associate
+ * // the value with tuple (loader, module, interfaces) and return it
+ * ClassLoader loader = ...;
+ * Class proxyClass = key.computeIfAbsent(loader, (ld, ky) -> {
+ *     List> intfcs = ky.key();
+ *     Module m = ky.parent().key();
+ *     Class clazz = defineProxyClass(ld, m, intfcs);
+ *     return clazz;
+ * });
+ * }
+ *

+ * {@code classLoaderValue.(classLoader, ...)} represents an operation + * to {@link #get}, {@link #putIfAbsent}, {@link #computeIfAbsent} or {@link #remove} + * a value associated with a (classLoader, classLoaderValue) tuple. ClassLoader + * instances and root-{@link ClassLoaderValue} instances are compared using + * identity equality while {@link Sub sub}-ClassLoaderValue instances define + * {@link #equals(Object) equality} in terms of equality of its + * {@link Sub#parent() parent} ClassLoaderValue and its + * {@link #key() key} component. + * + * @param the type of value(s) associated with the root-ClassLoaderValue and + * all its {@link #sub(Object) descendants}. + * @author Peter Levart + * @since 9 + */ +public final class ClassLoaderValue + extends AbstractClassLoaderValue, V> { + + /** + * Constructs new root-ClassLoaderValue representing its own namespace. + */ + public ClassLoaderValue() {} + + /** + * @return the key component of this root-ClassLoaderValue (itself). + */ + @Override + public ClassLoaderValue key() { + return this; + } + + /** + * root-ClassLoaderValue can only be equal to itself and has no predecessors. + */ + @Override + public boolean isEqualOrDescendantOf(AbstractClassLoaderValue clv) { + return equals(Objects.requireNonNull(clv)); + } +} --- old/test/java/lang/reflect/ClassLoaderValue/java.base/java/lang/reflect/ClassLoaderValueTest.java 2016-07-22 14:40:50.573363895 +0200 +++ /dev/null 2016-07-18 09:58:05.363251509 +0200 @@ -1,249 +0,0 @@ -/* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package java.lang.reflect; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Functional and concurrency test for ClassLoaderValue - * - * @author Peter Levart - */ -public class ClassLoaderValueTest { - - @SuppressWarnings("unchecked") - public static void main(String[] args) throws Exception { - - ClassLoaderValue[] clvs = {new ClassLoaderValue<>(), - new ClassLoaderValue<>()}; - - ClassLoader[] lds = {ClassLoader.getSystemClassLoader(), - ClassLoader.getPlatformClassLoader(), - null /* bootstrap class loader */}; - - Integer[] keys = new Integer[32]; - for (int i = 0; i < keys.length; i++) { - keys[i] = i + 128; - } - - try (AutoCloseable cleanup = () -> { - for (ClassLoaderValue clv : clvs) { - for (ClassLoader ld : lds) { - clv.removeAll(ld); - } - } - }) { - // 1st just one sequential pass of single-threaded validation - // which is easier to debug if it fails... - for (ClassLoaderValue clv : clvs) { - for (ClassLoader ld : lds) { - writeValidateOps(clv, ld, keys); - } - } - for (ClassLoaderValue clv : clvs) { - for (ClassLoader ld : lds) { - readValidateOps(clv, ld, keys); - } - } - - // 2nd the same in concurrent setting that also validates - // failure-isolation between threads and data-isolation between - // regions - (ClassLoader, ClassLoaderValue) pairs - of the storage - testConcurrentIsolation(clvs, lds, keys, TimeUnit.SECONDS.toMillis(3)); - } - } - - static void writeValidateOps(ClassLoaderValue clv, - ClassLoader ld, - Object[] keys) { - for (int i = 0; i < keys.length; i++) { - Object k = keys[i]; - Integer v1 = i; - Integer v2 = i + 333; - Integer pv; - boolean success; - - pv = clv.sub(k).putIfAbsent(ld, v1); - assertEquals(pv, null); - assertEquals(clv.sub(k).get(ld), v1); - - pv = clv.sub(k).putIfAbsent(ld, v2); - assertEquals(pv, v1); - assertEquals(clv.sub(k).get(ld), v1); - - success = clv.sub(k).remove(ld, v2); - assertEquals(success, false); - assertEquals(clv.sub(k).get(ld), v1); - - success = clv.sub(k).remove(ld, v1); - assertEquals(success, true); - assertEquals(clv.sub(k).get(ld), null); - - pv = clv.sub(k).putIfAbsent(ld, v2); - assertEquals(pv, null); - assertEquals(clv.sub(k).get(ld), v2); - - pv = clv.sub(k).computeIfAbsent(ld, (_ld, _clv) -> v1); - assertEquals(pv, v2); - assertEquals(clv.sub(k).get(ld), v2); - - success = clv.sub(k).remove(ld, v1); - assertEquals(success, false); - assertEquals(clv.sub(k).get(ld), v2); - - success = clv.sub(k).remove(ld, v2); - assertEquals(success, true); - assertEquals(clv.sub(k).get(ld), null); - - pv = clv.sub(k).computeIfAbsent(ld, (_ld, clv_k) -> { - try { - // nested get for same key should throw - clv_k.get(_ld); - throw new AssertionError("Unexpected code path"); - } catch (IllegalStateException e) { - // expected - } - try { - // nested putIfAbsent for same key should throw - clv_k.putIfAbsent(_ld, v1); - throw new AssertionError("Unexpected code path"); - } catch (IllegalStateException e) { - // expected - } - // nested remove for for same key and any value (even null) - // should return false - assertEquals(clv_k.remove(_ld, null), false); - assertEquals(clv_k.remove(_ld, v1), false); - assertEquals(clv_k.remove(_ld, v2), false); - try { - // nested computeIfAbsent for same key should throw - clv_k.computeIfAbsent(_ld, (__ld, _clv_k) -> v1); - throw new AssertionError("Unexpected code path"); - } catch (IllegalStateException e) { - // expected - } - // if everything above has been handled, we should succeed... - return v2; - }); - // ... and the result should be reflected in the CLV - assertEquals(pv, v2); - assertEquals(clv.sub(k).get(ld), v2); - - success = clv.sub(k).remove(ld, v2); - assertEquals(success, true); - assertEquals(clv.sub(k).get(ld), null); - - try { - clv.sub(k).computeIfAbsent(ld, (_ld, clv_k) -> { - throw new UnsupportedOperationException(); - }); - throw new AssertionError("Unexpected code path"); - } catch (UnsupportedOperationException e) { - // expected - } - assertEquals(clv.sub(k).get(ld), null); - } - } - - static void readValidateOps(ClassLoaderValue clv, - ClassLoader ld, - Object[] keys) { - for (int i = 0; i < keys.length; i++) { - Object k = keys[i]; - Integer v1 = i; - Integer v2 = i + 333; - Integer rv = clv.sub(k).get(ld); - if (!(rv == null || rv.equals(v1) || rv.equals(v2))) { - throw new AssertionError("Unexpected value: " + rv + - ", expected one of: null, " + v1 + ", " + v2); - } - } - } - - static void testConcurrentIsolation(ClassLoaderValue[] clvs, - ClassLoader[] lds, - Object[] keys, - long millisRuntime) { - ExecutorService exe = Executors.newCachedThreadPool(); - List> futures = new ArrayList<>(); - AtomicBoolean stop = new AtomicBoolean(); - for (ClassLoaderValue clv : clvs) { - for (ClassLoader ld : lds) { - // submit a task that exercises a mix of modifying - // and reading-validating operations in an isolated - // part of the storage. If isolation is violated, - // validation operations are expected to fail. - futures.add(exe.submit(() -> { - do { - writeValidateOps(clv, ld, keys); - } while (!stop.get()); - })); - // submit a task that just reads from the same part of - // the storage as above task. It should not disturb - // above task in any way and this task should never - // exhibit any failure although above task produces - // regular failures during lazy computation - futures.add(exe.submit(() -> { - do { - readValidateOps(clv, ld, keys); - } while (!stop.get()); - })); - } - } - // wait for some time - try { - Thread.sleep(millisRuntime); - } catch (InterruptedException e) { - throw new AssertionError(e); - } - // stop tasks - stop.set(true); - // collect results - AssertionError error = null; - for (Future future : futures) { - try { - future.get(); - } catch (InterruptedException | ExecutionException e) { - if (error == null) error = new AssertionError("Failure"); - error.addSuppressed(e); - } - } - exe.shutdown(); - if (error != null) throw error; - } - - static void assertEquals(Object actual, Object expected) { - if (!Objects.equals(actual, expected)) { - throw new AssertionError("Expected: " + expected + ", actual: " + actual); - } - } -} --- /dev/null 2016-07-18 09:58:05.363251509 +0200 +++ new/test/jdk/internal/util/concurrent/ClassLoaderValue/ClassLoaderValueTest.java 2016-07-22 14:40:50.472363183 +0200 @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import jdk.internal.util.concurrent.ClassLoaderValue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * @test + * @bug 8152115 + * @summary functional and concurrency test for ClassLoaderValue + * @modules java.base/jdk.internal.util.concurrent + * @author Peter Levart + */ +public class ClassLoaderValueTest { + + @SuppressWarnings("unchecked") + public static void main(String[] args) throws Exception { + + ClassLoaderValue[] clvs = {new ClassLoaderValue<>(), + new ClassLoaderValue<>()}; + + ClassLoader[] lds = {ClassLoader.getSystemClassLoader(), + ClassLoader.getPlatformClassLoader(), + null /* bootstrap class loader */}; + + Integer[] keys = new Integer[32]; + for (int i = 0; i < keys.length; i++) { + keys[i] = i + 128; + } + + try (AutoCloseable cleanup = () -> { + for (ClassLoaderValue clv : clvs) { + for (ClassLoader ld : lds) { + clv.removeAll(ld); + } + } + }) { + // 1st just one sequential pass of single-threaded validation + // which is easier to debug if it fails... + for (ClassLoaderValue clv : clvs) { + for (ClassLoader ld : lds) { + writeValidateOps(clv, ld, keys); + } + } + for (ClassLoaderValue clv : clvs) { + for (ClassLoader ld : lds) { + readValidateOps(clv, ld, keys); + } + } + + // 2nd the same in concurrent setting that also validates + // failure-isolation between threads and data-isolation between + // regions - (ClassLoader, ClassLoaderValue) pairs - of the storage + testConcurrentIsolation(clvs, lds, keys, TimeUnit.SECONDS.toMillis(3)); + } + } + + static void writeValidateOps(ClassLoaderValue clv, + ClassLoader ld, + Object[] keys) { + for (int i = 0; i < keys.length; i++) { + Object k = keys[i]; + Integer v1 = i; + Integer v2 = i + 333; + Integer pv; + boolean success; + + pv = clv.sub(k).putIfAbsent(ld, v1); + assertEquals(pv, null); + assertEquals(clv.sub(k).get(ld), v1); + + pv = clv.sub(k).putIfAbsent(ld, v2); + assertEquals(pv, v1); + assertEquals(clv.sub(k).get(ld), v1); + + success = clv.sub(k).remove(ld, v2); + assertEquals(success, false); + assertEquals(clv.sub(k).get(ld), v1); + + success = clv.sub(k).remove(ld, v1); + assertEquals(success, true); + assertEquals(clv.sub(k).get(ld), null); + + pv = clv.sub(k).putIfAbsent(ld, v2); + assertEquals(pv, null); + assertEquals(clv.sub(k).get(ld), v2); + + pv = clv.sub(k).computeIfAbsent(ld, (_ld, _clv) -> v1); + assertEquals(pv, v2); + assertEquals(clv.sub(k).get(ld), v2); + + success = clv.sub(k).remove(ld, v1); + assertEquals(success, false); + assertEquals(clv.sub(k).get(ld), v2); + + success = clv.sub(k).remove(ld, v2); + assertEquals(success, true); + assertEquals(clv.sub(k).get(ld), null); + + pv = clv.sub(k).computeIfAbsent(ld, (_ld, clv_k) -> { + try { + // nested get for same key should throw + clv_k.get(_ld); + throw new AssertionError("Unexpected code path"); + } catch (IllegalStateException e) { + // expected + } + try { + // nested putIfAbsent for same key should throw + clv_k.putIfAbsent(_ld, v1); + throw new AssertionError("Unexpected code path"); + } catch (IllegalStateException e) { + // expected + } + // nested remove for for same key and any value (even null) + // should return false + assertEquals(clv_k.remove(_ld, null), false); + assertEquals(clv_k.remove(_ld, v1), false); + assertEquals(clv_k.remove(_ld, v2), false); + try { + // nested computeIfAbsent for same key should throw + clv_k.computeIfAbsent(_ld, (__ld, _clv_k) -> v1); + throw new AssertionError("Unexpected code path"); + } catch (IllegalStateException e) { + // expected + } + // if everything above has been handled, we should succeed... + return v2; + }); + // ... and the result should be reflected in the CLV + assertEquals(pv, v2); + assertEquals(clv.sub(k).get(ld), v2); + + success = clv.sub(k).remove(ld, v2); + assertEquals(success, true); + assertEquals(clv.sub(k).get(ld), null); + + try { + clv.sub(k).computeIfAbsent(ld, (_ld, clv_k) -> { + throw new UnsupportedOperationException(); + }); + throw new AssertionError("Unexpected code path"); + } catch (UnsupportedOperationException e) { + // expected + } + assertEquals(clv.sub(k).get(ld), null); + } + } + + static void readValidateOps(ClassLoaderValue clv, + ClassLoader ld, + Object[] keys) { + for (int i = 0; i < keys.length; i++) { + Object k = keys[i]; + Integer v1 = i; + Integer v2 = i + 333; + Integer rv = clv.sub(k).get(ld); + if (!(rv == null || rv.equals(v1) || rv.equals(v2))) { + throw new AssertionError("Unexpected value: " + rv + + ", expected one of: null, " + v1 + ", " + v2); + } + } + } + + static void testConcurrentIsolation(ClassLoaderValue[] clvs, + ClassLoader[] lds, + Object[] keys, + long millisRuntime) { + ExecutorService exe = Executors.newCachedThreadPool(); + List> futures = new ArrayList<>(); + AtomicBoolean stop = new AtomicBoolean(); + for (ClassLoaderValue clv : clvs) { + for (ClassLoader ld : lds) { + // submit a task that exercises a mix of modifying + // and reading-validating operations in an isolated + // part of the storage. If isolation is violated, + // validation operations are expected to fail. + futures.add(exe.submit(() -> { + do { + writeValidateOps(clv, ld, keys); + } while (!stop.get()); + })); + // submit a task that just reads from the same part of + // the storage as above task. It should not disturb + // above task in any way and this task should never + // exhibit any failure although above task produces + // regular failures during lazy computation + futures.add(exe.submit(() -> { + do { + readValidateOps(clv, ld, keys); + } while (!stop.get()); + })); + } + } + // wait for some time + try { + Thread.sleep(millisRuntime); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + // stop tasks + stop.set(true); + // collect results + AssertionError error = null; + for (Future future : futures) { + try { + future.get(); + } catch (InterruptedException | ExecutionException e) { + if (error == null) error = new AssertionError("Failure"); + error.addSuppressed(e); + } + } + exe.shutdown(); + if (error != null) throw error; + } + + static void assertEquals(Object actual, Object expected) { + if (!Objects.equals(actual, expected)) { + throw new AssertionError("Expected: " + expected + ", actual: " + actual); + } + } +} --- old/test/java/lang/reflect/ClassLoaderValue/Driver.java 2016-07-22 14:40:50.779365346 +0200 +++ /dev/null 2016-07-18 09:58:05.363251509 +0200 @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/** - * @test - * @bug 8152115 - * @summary functional and concurrency test for ClassLoaderValue - * @build java.base/java.lang.reflect.ClassLoaderValueTest - * @run main Driver - */ -public class Driver { - public static void main(String[] args) throws Exception { - java.lang.reflect.ClassLoaderValueTest.main(args); - } -}