How a Jruby-Based Web Application Became Unusable Following its Redeployment. Lessons Learned

How a Jruby-Based Web Application Became Unusable Following its Redeployment. Lessons Learned

How a Jruby-Based Web Application Became Unusable Following its Redeployment. Lessons Learned

    Recently, Ashish Sahni, a GlassFish developer, reported a mysterious problem: He had deployed a Jruby-based web application to GlassFish, and when he tried to redeploy it, he noticed errors of the form java.lang.ThreadDeath in the server.log (in fact, the subsequent deployment of any web application would cause this error to be logged), and his web application had become unusable.

    How could the deployment and undeployment of a Jruby-based web application affect the subsequent deployment of unrelated web applications? And worse, how could it render the Jruby-based web application unusable?

    To investigate this issue, I looked at the stacktrace of the ThreadDeath error that was logged during redeployment:

    
      java.lang.ThreadDeath
        at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1325)
        ...
        at java.lang.Class.newInstance(Class.java:303)
        at java.security.Provider$Service.newInstance(Provider.java:1130)
        at sun.security.jca.GetInstance.getInstance(GetInstance.java:220)
        at sun.security.jca.GetInstance.getInstance(GetInstance.java:147)
        at java.security.Security.getImpl(Security.java:658)
        at java.security.MessageDigest.getInstance(MessageDigest.java:122)
        at java.io.ObjectStreamClass.computeDefaultSUID(ObjectStreamClass.java:1731)
        ...
        at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:302)
        at com.sun.enterprise.instance.SerializedDescriptorHelper.storeSerializedDescriptor(SerializedDescriptorHelper.java:239)
        at com.sun.enterprise.instance.SerializedDescriptorHelper.store(SerializedDescriptorHelper.java:143)
        at com.sun.enterprise.instance.SerializedDescriptorHelper.store(SerializedDescriptorHelper.java:123)
        at com.sun.enterprise.instance.BaseManager.saveAppDescriptor(BaseManager.java:684)
        ...
        at com.sun.enterprise.management.deploy.DeployThread.deploy(DeployThread.java:174)
        at com.sun.enterprise.management.deploy.DeployThread.run(DeployThread.java:210)
    

    I noticed the ThreadDeath error was thrown when the deployment code was attempting to serialize the web application's descriptor files, and in doing so, requested a MessageDigest instance for computing a serialization UID.

    The serialization (and deserialization) of application descriptors has been a performance enhancement developed by the deployment team. The code catches and logs any Throwable (along with its stacktrace) that may emerge from the attempt to serialize an application descriptor, and continues. Although the next reload of an application will take longer without the serialized descriptor, an exception during serialization does not cause any functional problems, i.e., the application will still deploy fine.

    The above stacktrace made me suspicious, and I instrumented the insertProviderAt(), addProvider(), and removeProvider() methods of java.security.Security to dump the stack of the calling thread. My suspicion was confirmed: It turned out that the Jruby code that was bundled with the web application registered its own provider (named BC) for cryptographic (in particular, MessageDigest) services, but never unregistered it during undeployment!

    The following stack trace shows how the provider was registered by Jruby:

    
      at java.lang.Thread.dumpStack(Thread.java:1158)
      at java.security.Security.insertProviderAt(Security.java:329)
      at org.jruby.RubyDigest.createDigest(RubyDigest.java:50)
      at org.jruby.libraries.DigestLibrary.load(DigestLibrary.java:40)
      at org.jruby.runtime.load.LoadService.smartLoad(LoadService.java:277)
      at org.jruby.runtime.load.LoadService.require(LoadService.java:299)
      at org.jruby.RubyDigest.createDigestSHA2(RubyDigest.java:104)
      at org.jruby.libraries.DigestLibrary$SHA2.load(DigestLibrary.java:63)
      at org.jruby.runtime.load.LoadService.smartLoad(LoadService.java:277)
      at org.jruby.runtime.load.LoadService.require(LoadService.java:299)
      at org.jruby.RubyKernel.require(RubyKernel.java:670)
      at org.jruby.RubyKernelInvokerSrequire1.call(Unknown Source)
      at org.jruby.runtime.callback.InvocationCallback.execute(InvocationCallback.java:49)
      at org.jruby.internal.runtime.methods.FullFunctionCallbackMethod.internalCall(FullFunctionCallbackMethod.java:76)
      at org.jruby.internal.runtime.methods.DynamicMethod.call(DynamicMethod.java:69)
      at org.jruby.RubyObject.callMethod(RubyObject.java:487)
      at org.jruby.RubyObject.callMethod(RubyObject.java:395)
      at org.jruby.evaluator.EvaluationState.evalInternal(EvaluationState.java:770)
      at org.jruby.evaluator.EvaluationState.evalInternal(EvaluationState.java:298)
      at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:163)
      at org.jruby.evaluator.EvaluationState.evalInternal(EvaluationState.java:1317)
      at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:163)
      at org.jruby.RubyObject.eval(RubyObject.java:563)
      ....
    

    but this provider was never unregistered.

    You may wonder why the serialization of application descriptors during subsequent deployments would pick up a MessageDigest algorithm implementation from the cryptographic provider that was registered by Jruby, when the Jruby-based web application had already been undeployed, and why any attempt to acquire such a MessageDigest implementation would result in a ThreadDeath error.

    Here's an explanation: Jruby inserted its cryptographic provider at position 2 (using java.security.Security.insertProviderAt()), thereby preempting the default cryptographic provider for MessageDigest (and other algorithms) named sun.security.provider.Sun, which ships with Sun's JRE and which, by default, is registered in 2nd position, i.e., takes precedence over any providers registered with a higher position number (the lower the position number, the higher the priority during cryptographic algorithm implementation lookups for which no provider name was specified). (The Java runtime's list of cryptographic service providers and their precedence order is initialized from the runtime's lib/security/java.security file resource. Any changes to this list, which require appropriate security permissions, have VM-wide visibility.)

    Now, this would not have been an issue had Jruby unregistered its provider during the undeployment of its bundling web application. But it never did, causing the application descriptor serialization during the subsequent redeployment to acquire a MessageDigest algorithm implementation whose underlying classloader (an instance of org.apache.catalina.loader.WebappClassLoader) had been deactivated when the Jruby-based web application was undeployed. And was does a deactivated org.apache.catalina.loader.WebappClassLoader instance do when it is being asked to load a resource? It throws a ThreadDeath error!

    This also explains why the Jruby-based web application was unusable following its redeployment: The Jruby code ended up getting the same BC provider and its MessageDigest implementation that had been registered and loaded during the initial deployment, and whose classloader had been deactivated during undeployment, so any attempt to perform any digest operations by Jruby started failing in the redeployed version of the bundling web application.

    Lessons Learned:

    • Jruby should have unregistered its cryptographic provider during undeployment, by calling java.security.Security.removeProvider("BC"). This could have been done during the invocation of org.jruby.webapp.AbstractRailsServlet.destroy(). In addition, Jruby should not have registered its provider with such a high priority (using java.security.Security.insertProviderAt()). Instead, it should have registered its provider using java.security.Security.addProvider(), which inserts the provider at the next available position, which normally is at the end of the list, and should have requested its provider's MessageDigest algorithm implementation by passing its provider's name to the java.security.MessageDigest.getInstance() method.

    • The web container should have been more resilient against the case where a web application registers a cryptographic provider, but then forgets to unregister it during undeployment. In other words, the web container should have cleaned up after the Jruby-based web application (or any other web application for that matter) during its undeployment, by unregistering any of its cryptographic providers that were left behind. This can be accomplished by the following code:
      
          import java.security.\*;
      
          Provider[] providers = Security.getProviders();
          if (providers != null) {
              for (Provider provider : providers) {
                  if (provider.getClass().getClassLoader() == this) {
                      Security.removeProvider(provider.getName());
                  }
              }
          }
      

      which has been added to org.apache.catalina.loader.WebappClassLoader.stop() and committed to GlassFish v2 Build 44.

Comments:

Just to clarify, for anyone reading: - Our registering the security provider in the first place was a mistake. - JRuby 0.9.9 no longer registers a security provider. It either uses BC directly, or defers to whatever security provider the container provides. So it's an interesting lesson (for me as well) in what not to do with security providers, but it won't affect any future users of JRuby.

Posted by Charles Oliver Nutter on April 28, 2007 at 04:16 AM PDT #

Jan, thanks for the great work you did on solving this issue. Keep up the good work ! -Ashish

Posted by Ashish on May 02, 2007 at 05:52 AM PDT #

[Trackback] One new subscriber from Anothr Alerts

Posted by anothr user on June 12, 2007 at 02:29 PM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

jluehe

Search

Categories
Archives
« February 2015
SunMonTueWedThuFriSat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
       
       
Today