glassfish
  1. glassfish
  2. GLASSFISH-21522

NullPointerException during WebDirContext.lookupFromJars

    Details

    • Type: Bug Bug
    • Status: Resolved
    • Priority: Critical Critical
    • Resolution: Fixed
    • Affects Version/s: 4.0, 4.1, 4.1.1
    • Fix Version/s: 5.0
    • Labels:
      None
    • Environment:

      Mac OS X El Capitan, JDK 8u74

      Description

      When my application starts, the ServletContextListener is called and starts up several threads to run in the background. Sometimes it works, and sometimes I get NPEs that keep the application from starting correctly:

      Caused by: java.lang.NullPointerException
      at org.apache.naming.resources.WebDirContext.lookupFromJars(WebDirContext.java:325)
      at org.apache.naming.resources.WebDirContext.getAttributes(WebDirContext.java:298)
      at org.apache.naming.resources.BaseDirContext.getAttributes(BaseDirContext.java:787)
      at org.apache.naming.resources.ProxyDirContext.cacheLoad(ProxyDirContext.java:1533)
      at org.apache.naming.resources.ProxyDirContext.cacheLookup(ProxyDirContext.java:1456)
      at org.apache.naming.resources.ProxyDirContext.lookup(ProxyDirContext.java:274)
      at org.glassfish.web.loader.WebappClassLoader.findResourceInternalFromRepositories(WebappClassLoader.java:2892)
      at org.glassfish.web.loader.WebappClassLoader.findResourceInternal(WebappClassLoader.java:2842)
      at org.glassfish.web.loader.WebappClassLoader.findClassInternal(WebappClassLoader.java:2736)
      at org.glassfish.web.loader.WebappClassLoader.findClass(WebappClassLoader.java:1194)
      at org.glassfish.web.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1750)
      at org.glassfish.web.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1633)

      After extensive debugging with the sources for 4.1.1, I found that there are two competing methods inside of org.glassfish.loader.WebappClassLoader:

      protected boolean openJARs() {
      if (started && (jarFiles.length > 0)) {
      lastJarAccessed = System.currentTimeMillis();
      if (jarFiles[0] == null) {
      for (int i = 0; i < jarFiles.length; i++) {
      try

      { jarFiles[i] = new JarFile(jarRealFiles[i]); }

      catch (IOException e) {
      if (logger.isLoggable(Level.FINE))

      { logger.log(Level.FINE, "Failed to open JAR", e); }

      for (int j = 0; j < i; j++) {
      try

      { jarFiles[j].close(); }

      catch (Throwable t)

      { // Ignore }

      }
      return false;
      }
      }
      }
      }
      return true;
      }

      public void closeJARs(boolean force) {
      if (jarFiles.length > 0) {
      synchronized (jarFiles) {
      if (force || (System.currentTimeMillis()
      > (lastJarAccessed + 90000))) {
      for (int i = 0; i < jarFiles.length; i++) {
      try {
      if (jarFiles[i] != null)

      { jarFiles[i].close(); jarFiles[i] = null; }

      } catch (IOException e) {
      if (logger.isLoggable(Level.FINE))

      { logger.log(Level.FINE, "Failed to close JAR", e); }

      }
      }
      }
      }
      }
      }

      It turns out that closeJARs() is called from org.apache.catalina.core.StandardContext.start, at the end of the startup routine:

      // Close all JARs right away to avoid always opening a peak number
      // of files on startup
      if (getLoader() instanceof WebappLoader)

      { ((WebappLoader) getLoader()).closeJARs(true); }

      The obvious problem is that while closeJARs() is being run in the startup thread, the openJARs() method is also being run by the ServletContextListener as it loads classes during its own initialization routines. Since openJARs() is not synchronized on the jarFiles object like closeJARs() is, both methods run concurrently and overwrite the array items, creating an array with 'holes' in it, where some items are null and some are not. Thus, while doing a lookup in WebDirContext.lookupFromJars, we hit a null array item in the jarFiles array and get an NPE.

      It seems like the fix for this should be to simply add a 'synchronized' block in openJARs() after the first 'if' check; however, using jarFiles as the lock for all these operations seems like a poor choice, especially because the addJar() and stop() methods also don't synchronize access and change the object reference for the lock. It seems like a different, final Object should be used instead.

        Activity

        Hide
        cplummer added a comment -

        FYI, I've also engaged the Payara devs on this issue and created an application that can reproduce the issue:

        https://github.com/payara/Payara/issues/693

        Show
        cplummer added a comment - FYI, I've also engaged the Payara devs on this issue and created an application that can reproduce the issue: https://github.com/payara/Payara/issues/693
        Hide
        Vinay Vishal added a comment -

        Sending appserver/web/war-util/src/main/java/org/glassfish/web/loader/WebappClassLoader.java
        Transmitting file data .
        Committed revision 64364.

        Show
        Vinay Vishal added a comment - Sending appserver/web/war-util/src/main/java/org/glassfish/web/loader/WebappClassLoader.java Transmitting file data . Committed revision 64364.

          People

          • Assignee:
            Vinay Vishal
            Reporter:
            cplummer
          • Votes:
            1 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: