### Eclipse Workspace Patch 1.0 #P jackrabbit-core Index: src/test/java/org/apache/jackrabbit/core/deadlock/DeadlockTest.java =================================================================== --- src/test/java/org/apache/jackrabbit/core/deadlock/DeadlockTest.java (revision 0) +++ src/test/java/org/apache/jackrabbit/core/deadlock/DeadlockTest.java (working copy) @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.core.deadlock; + +import javax.jcr.Node; +import javax.jcr.Session; + +import org.apache.jackrabbit.core.state.DeadlockUtils; +import org.apache.jackrabbit.test.AbstractJCRTest; + +public class DeadlockTest extends AbstractJCRTest { + + static final String TEST_ROOT = "t" + System.currentTimeMillis() + "_"; + + private Session session; + + @Override + public void setUp() throws Exception { + super.setUp(); + session = testRootNode.getSession(); + } + + public void testConcurrent() throws Exception { + if (!DeadlockUtils.INSTRUMENTATION) { + System.out.println("Instrumentation is disabled, tests will not find a deadlock"); + return; + } + if (DeadlockUtils.PATCH) { + System.out.println("Patch is enabled: this test should pass"); + } else { + System.out.println("Patch is disabled: this test should fail"); + } + for(int i=0; i < 1000; i++) { + System.out.println("i: " + i); + testConcurrent(i); + } + } + + private void testConcurrent(final int index) throws Exception { + int threadCount = 3; + init(session, index); + Thread[] threads = new Thread[threadCount]; + DeadlockUtils.clearBuffer(); + for(int i=0; i /** @@ -116,6 +147,20 @@ */ @Override public synchronized void copy(ItemState state, boolean syncModCount) { + if (DeadlockUtils.INSTRUMENTATION) { + try { + if (isTestNode()) { + DeadlockUtils.log(" NodeState.copy (artificially slow)"); + } + synchronized (NodeState.class) { + while (Math.random() < 0.5) { + Thread.sleep(1); + } + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } synchronized (state) { NodeState nodeState = (NodeState) state; id = nodeState.id; Index: src/main/java/org/apache/jackrabbit/core/state/ItemState.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/state/ItemState.java (revision 1691398) +++ src/main/java/org/apache/jackrabbit/core/state/ItemState.java (working copy) @@ -146,11 +146,22 @@ * Pull state information from overlayed state. */ synchronized void pull() { + boolean doLog = this instanceof NodeState && ((NodeState) this).isTestNode(); + if (doLog) { + DeadlockUtils.log(" NodeState " + this + " .pull() {"); + DeadlockUtils.log(DeadlockUtils.getStackTrace()); + } + try { ItemState state = overlayedState; if (state != null) { // sync modification count copy(state, true); } + } finally { + if (doLog) { + DeadlockUtils.log(" }"); + } + } } /** Index: src/main/java/org/apache/jackrabbit/core/state/DeadlockUtils.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/state/DeadlockUtils.java (revision 0) +++ src/main/java/org/apache/jackrabbit/core/state/DeadlockUtils.java (working copy) @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jackrabbit.core.state; + +public class DeadlockUtils { + + public static final boolean INSTRUMENTATION = true; + + public static final boolean PATCH = false; + + private static StringBuilder LOG_BUFFER = new StringBuilder(); + + public static boolean isTestThread() { + if (!INSTRUMENTATION) { + return false; + } + String threadName = Thread.currentThread().getName(); + return threadName.startsWith("Thread "); + } + + public static void log(String message) { + if (!INSTRUMENTATION) { + return; + } + if (!isTestThread()) { + return; + } + String threadName = Thread.currentThread().getName(); + if (threadName.endsWith(" init")) { + return; + } + LOG_BUFFER.append(threadName).append(' ').append(message).append('\n'); + } + + public static String getStackTrace() { + StringBuilder buff = new StringBuilder(); + for(StackTraceElement e : Thread.currentThread().getStackTrace()) { + buff.append(" ").append(e.toString()).append('\n'); + } + return buff.toString(); + } + + public static void clearBuffer() { + LOG_BUFFER.setLength(0); + } + + public static String getLog() { + return LOG_BUFFER.toString(); + } + +} Index: src/test/java/org/apache/jackrabbit/core/deadlock/repository.xml =================================================================== --- src/test/java/org/apache/jackrabbit/core/deadlock/repository.xml (revision 0) +++ src/test/java/org/apache/jackrabbit/core/deadlock/repository.xml (working copy) @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java (revision 1691398) +++ src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java (working copy) @@ -803,7 +803,22 @@ visibleState = transientState; } } - dispatcher.notifyStateCreated(visibleState); + boolean notifyTransientSpace = true; + if (visibleState instanceof NodeState) { + // no need to push "node created" to transient space: + // either the transient already knows about this state, or it doesn't + // the problem is, this can lead to a deadlock + notifyTransientSpace = false; + if (!DeadlockUtils.PATCH) { + // disable this patch + notifyTransientSpace = true; + } + } else { + notifyTransientSpace = true; + } + if (notifyTransientSpace) { + dispatcher.notifyStateCreated(visibleState); + } } /** @@ -978,13 +993,22 @@ public NodeState makePersistent(NodeState transientState) throws RepositoryException { NodeState localState = stateMgr.getOrCreateLocalState(transientState); - + boolean doLog = localState.isTestNode(); + if (doLog) { + DeadlockUtils.log(" NodeState.makePersistent() {"); + } + try { synchronized (localState) { // copy state from transient state: localState.copy(transientState, true); // make state persistent store(localState); } + } finally { + if (doLog) { + DeadlockUtils.log(" }"); + } + } // disconnect the transient item state disconnectTransientItemState(transientState);