Uploaded image for project: 'Jackrabbit Oak'
  1. Jackrabbit Oak
  2. OAK-4538

IndexDefinition.createCodec class loading deadlock

    XMLWordPrintableJSON

Details

    • Bug
    • Status: Closed
    • Major
    • Resolution: Fixed
    • None
    • 1.4.5, 1.5.5, 1.6.0
    • lucene, query
    • None

    Description

      Sometimes, when initializing an Oak Lucene index, a class loading deadlock can occur. Unfortunately, no deadlock is reported by the JVM when creating a full thread dump, but the thread dump typically shows the threads below. The root cause seems to be LUCENE-6482, and the reason for that is described in http://ternarysearch.blogspot.it/2013/07/static-initialization-deadlock.html

      I have created a simple, reproducible test case, and found a simple workaround in Oak, which is to load the OakCodec before a custom codec. This ensures the class OakCodec, and all superclasses, are loaded before the static initializer of Codec is run. Test case see below (un-commenting the commented line will make it work, otherwise the test results in a deadlock most of the time).

      java.lang.Thread.State: RUNNABLE
      	at org.apache.jackrabbit.oak.plugins.index.lucene.IndexDefinition.createCodec(IndexDefinition.java:1301)
      	at org.apache.jackrabbit.oak.plugins.index.lucene.IndexDefinition.<init>(IndexDefinition.java:260)
      	at org.apache.jackrabbit.oak.plugins.index.lucene.IndexDefinition.<init>(IndexDefinition.java:228)
      	at org.apache.jackrabbit.oak.plugins.index.lucene.IndexNode.open(IndexNode.java:48)
      	at org.apache.jackrabbit.oak.plugins.index.lucene.IndexTracker.findIndexNode(IndexTracker.java:179)
      	- locked <0x00000007ff915448> (a org.apache.jackrabbit.oak.plugins.index.lucene.IndexTracker)
      	at org.apache.jackrabbit.oak.plugins.index.lucene.IndexTracker.acquireIndexNode(IndexTracker.java:154)
      	at org.apache.jackrabbit.oak.plugins.index.lucene.LucenePropertyIndex.getPlans(LucenePropertyIndex.java:250)
      	at org.apache.jackrabbit.oak.query.QueryImpl.getBestSelectorExecutionPlan(QueryImpl.java:1016)
      	at org.apache.jackrabbit.oak.query.QueryImpl.getBestSelectorExecutionPlan(QueryImpl.java:949)
      	at org.apache.jackrabbit.oak.query.ast.SelectorImpl.prepare(SelectorImpl.java:288)
      	at org.apache.jackrabbit.oak.query.QueryImpl.prepare(QueryImpl.java:631)
      	at org.apache.jackrabbit.oak.query.QueryEngineImpl.prepareAndSelect(QueryEngineImpl.java:298)
      	at org.apache.jackrabbit.oak.query.QueryEngineImpl.executeQuery(QueryEngineImpl.java:273)
      	at org.apache.jackrabbit.oak.query.QueryEngineImpl.executeQuery(QueryEngineImpl.java:233)
      	at org.apache.jackrabbit.oak.plugins.identifier.IdentifierManager.resolveUUID(IdentifierManager.java:314)
      	at org.apache.jackrabbit.oak.plugins.identifier.IdentifierManager.resolveUUID(IdentifierManager.java:308)
      	at org.apache.jackrabbit.oak.plugins.identifier.IdentifierManager.resolveUUID(IdentifierManager.java:304)
      	at org.apache.jackrabbit.oak.plugins.identifier.IdentifierManager.getTree(IdentifierManager.java:133)
      	at org.apache.jackrabbit.oak.security.authentication.token.TokenProviderImpl.getTokenInfo(TokenProviderImpl.java:250)
      	at org.apache.jackrabbit.oak.security.authentication.token.TokenAuthentication.validateCredentials(TokenAuthentication.java:81)
      
      
      "aysnc-index-update-fulltext-async" prio=5 tid=0x00007fe845e51800 nid=0xb407 in Object.wait() [0x0000700005f2f000]
         java.lang.Thread.State: RUNNABLE
      	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
      	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
      	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
      	at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
      	at java.lang.Class.newInstance(Class.java:379)
      	at org.apache.lucene.util.NamedSPILoader.reload(NamedSPILoader.java:67)
      	- locked <0x00000007d2ba9120> (a org.apache.lucene.util.NamedSPILoader)
      	at org.apache.lucene.util.NamedSPILoader.<init>(NamedSPILoader.java:45)
      	at org.apache.lucene.util.NamedSPILoader.<init>(NamedSPILoader.java:37)
      	at org.apache.lucene.codecs.Codec.<clinit>(Codec.java:41)
      	at org.apache.jackrabbit.oak.plugins.index.lucene.IndexDefinition.createCodec(IndexDefinition.java:1299)
      	at org.apache.jackrabbit.oak.plugins.index.lucene.IndexDefinition.<init>(IndexDefinition.java:260)
      	at org.apache.jackrabbit.oak.plugins.index.lucene.IndexDefinition.<init>(IndexDefinition.java:224)
      	at org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexEditorContext.<init>(LuceneIndexEditorContext.java:170)
      	at org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexEditor.<init>(LuceneIndexEditor.java:132)
      	at org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexEditorProvider.getIndexEditor(LuceneIndexEditorProvider.java:72)
      	at org.apache.jackrabbit.oak.plugins.index.CompositeIndexEditorProvider.getIndexEditor(CompositeIndexEditorProvider.java:74)
      	at org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardIndexEditorProvider.getIndexEditor(WhiteboardIndexEditorProvider.java:52)
      	at org.apache.jackrabbit.oak.plugins.index.IndexUpdate.collectIndexEditors(IndexUpdate.java:201)
      	at org.apache.jackrabbit.oak.plugins.index.IndexUpdate.enter(IndexUpdate.java:144)
      	at org.apache.jackrabbit.oak.spi.commit.VisibleEditor.enter(VisibleEditor.java:57)
      	at org.apache.jackrabbit.oak.spi.commit.EditorDiff.process(EditorDiff.java:49)
      	at org.apache.jackrabbit.oak.plugins.index.AsyncIndexUpdate.updateIndex(AsyncIndexUpdate.java:510)
      	at org.apache.jackrabbit.oak.plugins.index.AsyncIndexUpdate.runWhenPermitted(AsyncIndexUpdate.java:439)
      

      Test case:

      public class StaticInitDeadlock {
      
          public static class Codec {
      
              static {
                  debug("<clinit>");
                  forName("OtherCodec");
                  forName("OakCodec");
                  debug("<clinit> done");
              }
      
              static Codec forName(String name) {
                  debug("Codec.forName(" + name + ")");
                  try {
                      return (Codec) Class.forName("StaticInitDeadlock$" + name)
                              .newInstance();
                  } catch (Exception e) {
                      e.printStackTrace();
                      return null;
                  }
              }
      
          }
      
          static class OtherCodec extends Codec {
              static {
                  debug("OtherCodec <clinit>");
              }
          }
      
          static class OakCodec extends Codec {
              static {
                  debug("OakCodec <clinit>");
              }
          }
      
          static void createCodec(String codecName) {
              debug("createCodec (" + codecName + ")");
              if (codecName != null) {
                  // new OakCodec();
                  Codec.forName(codecName);
              } else {
                  new OakCodec();
              }
          }
      
          public static void main(String[] args) throws InterruptedException {
              Thread thread = new Thread("Thread 2") {
                  @Override
                  public void run() {
                      debug("loading other codec...");
                      createCodec("OtherCodec");
                      debug("loading other codec done!");
                  }
              };
              thread.start();
              Thread.currentThread().setName("Thread 1");
              debug("loading default (oak) codec...");
              createCodec(null);
              debug("joining thread...");
              thread.join();
              debug("done!");
          }
      
          static void debug(String msg) {
              Thread t = Thread.currentThread();
              StackTraceElement[] st = t.getStackTrace();
              int depth = st.length;
              String indent = new String(new char[depth]).replace("\0", " ");
              System.out.println(t.getName() + ": " + indent + st[2] + " " + msg);
          }
      
      }
      

      Output when deadlocked:

      Thread 2:    StaticInitDeadlock$1.run(StaticInitDeadlock.java:56) loading other codec...
      Thread 1:    StaticInitDeadlock.main(StaticInitDeadlock.java:63) loading default (oak) codec...
      Thread 2:     StaticInitDeadlock.createCodec(StaticInitDeadlock.java:43) createCodec (OtherCodec)
      Thread 1:     StaticInitDeadlock.createCodec(StaticInitDeadlock.java:43) createCodec (null)
      Thread 2:      StaticInitDeadlock$Codec.<clinit>(StaticInitDeadlock.java:11) <clinit>
      Thread 2:       StaticInitDeadlock$Codec.forName(StaticInitDeadlock.java:18) Codec.forName(OtherCodec)
      Thread 2:          StaticInitDeadlock$OtherCodec.<clinit>(StaticInitDeadlock.java:32) OtherCodec <clinit>
      Thread 2:       StaticInitDeadlock$Codec.forName(StaticInitDeadlock.java:18) Codec.forName(OakCodec)
      

      Output without deadlock:

      Thread 1:    StaticInitDeadlock.main(StaticInitDeadlock.java:63) loading default (oak) codec...
      Thread 2:    StaticInitDeadlock$1.run(StaticInitDeadlock.java:56) loading other codec...
      Thread 1:     StaticInitDeadlock.createCodec(StaticInitDeadlock.java:43) createCodec (null)
      Thread 2:     StaticInitDeadlock.createCodec(StaticInitDeadlock.java:43) createCodec (OtherCodec)
      Thread 1:      StaticInitDeadlock$Codec.<clinit>(StaticInitDeadlock.java:11) <clinit>
      Thread 1:       StaticInitDeadlock$Codec.forName(StaticInitDeadlock.java:18) Codec.forName(OtherCodec)
      Thread 1:          StaticInitDeadlock$OtherCodec.<clinit>(StaticInitDeadlock.java:32) OtherCodec <clinit>
      Thread 1:       StaticInitDeadlock$Codec.forName(StaticInitDeadlock.java:18) Codec.forName(OakCodec)
      Thread 1:      StaticInitDeadlock$Codec.<clinit>(StaticInitDeadlock.java:14) <clinit> done
      Thread 1:      StaticInitDeadlock$OakCodec.<clinit>(StaticInitDeadlock.java:38) OakCodec <clinit>
      Thread 1:    StaticInitDeadlock.main(StaticInitDeadlock.java:65) joining thread...
      Thread 2:      StaticInitDeadlock$Codec.forName(StaticInitDeadlock.java:18) Codec.forName(OtherCodec)
      Thread 2:    StaticInitDeadlock$1.run(StaticInitDeadlock.java:58) loading other codec done!
      Thread 1:    StaticInitDeadlock.main(StaticInitDeadlock.java:67) done!
      

      Attachments

        Issue Links

          Activity

            People

              thomasm Thomas Mueller
              thomasm Thomas Mueller
              Votes:
              0 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: