Review Request JDK-8170772: ResourceBundle improper caching causes tools/javadoc tests intermittently

Peter Levart peter.levart at gmail.com
Mon Dec 12 15:10:10 UTC 2016


Hi Mandy (once again for the list),

On 12/09/2016 05:49 PM, Mandy Chung wrote:
> Naoto,  > > Can you review this ResourceBundle caching fix?  The caller module 
 > may be different than the specified module to > 
ResourceBundle.getBundle(String, Module) method and it should also > 
part of the cache key. > > 
http://cr.openjdk.java.net/~mchung/jdk9/webrevs/8170772/webrev.00/ > > 
The new test shows the issue there and the first loading of the > 
resource bundle of a specific module (success or fail) will be put in > 
the cache and used by subsequent calls. > > Thanks Mandy


I think that now callerModule is part of CacheKey, caching logic could 
be simplified. 1st the getBundleImpl method in line 1629:

     private static ResourceBundle getBundleImpl(String baseName,
                                                 Locale locale,
                                                 Class<?> caller,
                                                 ClassLoader loader,
                                                 Control control) {
         if (caller != null && caller.getModule().isNamed()) {
             Module module = caller.getModule();
             ClassLoader ml = getLoader(module);
             // get resource bundles for a named module only
             // if loader is the module's class loader
             if (loader == ml || (ml == null && loader == 
RBClassLoader.INSTANCE)) {
                 return getBundleImpl(module, module, loader, baseName, 
locale, control);
             }
         }
         // find resource bundles from unnamed module
         Module unnamedModule = loader != null
             ? loader.getUnnamedModule()
             : ClassLoader.getSystemClassLoader().getUnnamedModule();

         if (caller == null) {
             throw new InternalError("null caller");
         }

         Module callerModule = caller.getModule();
         return getBundleImpl(callerModule, unnamedModule, loader, 
baseName, locale, control);
     }


... could be cleaned up a bit without changing its semantics:

     private static ResourceBundle getBundleImpl(String baseName,
                                                 Locale locale,
                                                 Class<?> caller,
                                                 ClassLoader loader,
                                                 Control control) {
         if (caller == null) {
             throw new InternalError("null caller");
         }
         Module callerModule = caller.getModule();

         if (callerModule.isNamed()) {
             ClassLoader ml = getLoader(callerModule);
             // get resource bundles for a named module only
             // if loader is the module's class loader
             if (loader == ml || (ml == null && loader == 
RBClassLoader.INSTANCE)) {
                 return getBundleImpl(callerModule, callerModule, 
loader, baseName, locale, control);
             }
         }

         // find resource bundles from unnamed module
         Module unnamedModule = loader != null
             ? loader.getUnnamedModule()
             : ClassLoader.getSystemClassLoader().getUnnamedModule();

         return getBundleImpl(callerModule, unnamedModule, loader, 
baseName, locale, control);
     }


Next, I checked all callers of this method (there are 3 of them in 
lines: 1367, 1589, 1615) and all of them guard against passing a null 
'loader' to this method:

     @CallerSensitive
     public static ResourceBundle getBundle(String baseName, Locale locale,
                                            ClassLoader loader)
     {
         if (loader == null) {
             throw new NullPointerException();
         }
         Class<?> caller = Reflection.getCallerClass();
         return getBundleImpl(baseName, locale, caller, loader, 
getDefaultControl(caller, baseName));
     }

...

     @CallerSensitive
     public static ResourceBundle getBundle(String baseName, Locale 
targetLocale,
                                            ClassLoader loader, Control 
control) {
         if (loader == null || control == null) {
             throw new NullPointerException();
         }
         Class<?> caller = Reflection.getCallerClass();
         checkNamedModule(caller);
         return getBundleImpl(baseName, targetLocale, caller, loader, 
control);
     }

...

     private static ResourceBundle getBundleImpl(String baseName,
                                                 Locale locale,
                                                 Class<?> caller,
                                                 Control control) {
         return getBundleImpl(baseName, locale, caller, 
getLoader(caller), control);
     }

     private static ClassLoader getLoader(Class<?> caller) {
         ClassLoader cl = caller == null ? null : caller.getClassLoader();
         if (cl == null) {
             // When the caller's loader is the boot class loader, cl is 
null
             // here. In that case, ClassLoader.getSystemClassLoader() may
             // return the same class loader that the application is
             // using. We therefore use a wrapper ClassLoader to create a
             // separate scope for bundles loaded on behalf of the Java
             // runtime so that these bundles cannot be returned from the
             // cache to the application (5048280).
             cl = RBClassLoader.INSTANCE;
         }
         return cl;
     }


Therefore the above method can be simplified further:

     private static ResourceBundle getBundleImpl(String baseName,
                                                 Locale locale,
                                                 Class<?> caller,
                                                 ClassLoader loader,
                                                 Control control) {
         if (caller == null) {
             throw new InternalError("null caller");
         }
         if (loader == null) {
             throw new InternalError("null loader");
         }
         Module callerModule = caller.getModule();

         if (callerModule.isNamed()) {
             ClassLoader ml = getLoader(callerModule);
             // get resource bundles for a named module only
             // if loader is the module's class loader
             if (loader == ml || (ml == null && loader == 
RBClassLoader.INSTANCE)) {
                 return getBundleImpl(callerModule, callerModule, 
loader, baseName, locale, control);
             }
         }

         // find resource bundles from unnamed module
         Module unnamedModule = loader.getUnnamedModule();
         return getBundleImpl(callerModule, unnamedModule, loader, 
baseName, locale, control);
     }



...here we can see that (callerModule, module, loader) triple passed to 
downstream getBundleImpl is either (callerModule, callerModule, 
callerModule's class loader) - with a RBClassLoader.INSTANCE substitute 
for bootstrap class loader, when the callerModule is a named module and 
requested loader is the callerModule's loader, or (callerModule, 
loader's unnamed module, loader) when loader is not callerModule's 
loader or callerModule is unnamed.

other two callers of the downstream getBundleImpl are the 
JavaUtilResourceBundleAccess method:

                 public ResourceBundle getBundle(String baseName, Locale 
locale, Module module) {
                     // use the given module as the caller to bypass the 
access check
                     return getBundleImpl(module, module, getLoader(module),
                                          baseName, locale, 
Control.INSTANCE);
                 }

...which is used in logging - the module passed here can be either named 
or unnamed;

... and a method invoked from new JDK 9 public getBundle() methods 
taking explicit Module argument:

     private static ResourceBundle getBundleFromModule(Class<?> caller,
                                                       Module module,
                                                       String baseName,
                                                       Locale locale,
                                                       Control control) {
         Objects.requireNonNull(module);
         Module callerModule = caller.getModule();
         if (callerModule != module) {
             SecurityManager sm = System.getSecurityManager();
             if (sm != null) {
                 sm.checkPermission(GET_CLASSLOADER_PERMISSION);
             }
         }
         return getBundleImpl(callerModule, module, getLoader(module), 
baseName, locale, control);
     }

In all of these cases, the loader passed to downstream getBundleImpl is 
the module's (2nd argument 'module') class loader (or a special 
substitute for bootstrap loader).

Considering all this, I think class loader is not needed any more as the 
CacheKey component. The distinction between scopes of system class 
loader (when the caller is not a bootstrap class) and the 
RBClassLoader.INSTANCE (when the caller is the bootstrap class) is also 
not needed any more since the callerModule is now part of CacheKey.

I modified your patch (just ResourceBundle.java) to include all these 
simplifications and some cleanup:

http://cr.openjdk.java.net/~plevart/jdk9-dev/8170772_ResourceBundle.caching/webrev.01/


This modification also contains a re-interpretation of clearCache() 
methods. Both existing clearCahe() methods together with the additional 
@since 9 method contain the following wording:

"Removes all resource bundles from the cache that have been loaded by 
the caller's / given module..."

What does it meant for a bundle to be loaded *by* some module? I think 
the right interpretation is that this is the caller module (the one that 
invokes ResourceBundle.getBundle() method). The module that calls 
ResourceBundle.getBundle() is usually also the module that is 
responsible for clearing the cache of entries that were cached by its 
loading requests, isn't it?

So, what do you think?

Regards, Peter



More information about the core-libs-dev mailing list