diff options
author | Eike Stepper | 2013-08-05 18:24:49 +0000 |
---|---|---|
committer | Eike Stepper | 2013-08-05 18:24:49 +0000 |
commit | 903a2169001c44b724759e8ed66517c831a2fbdb (patch) | |
tree | dcd2fc530042d4723bc2d82924c85e77c11a17c0 | |
parent | 4f29ce12ff912157d32dd166f3a6e8aeac71d2a9 (diff) | |
download | cdo-bugs/411939.tar.gz cdo-bugs/411939.tar.xz cdo-bugs/411939.zip |
[411939] CDOSessionImpl can "freeze" during invalidation reorderingbugs/411939
https://bugs.eclipse.org/bugs/show_bug.cgi?id=411939
11 files changed, 438 insertions, 339 deletions
diff --git a/plugins/org.eclipse.emf.cdo.server/META-INF/MANIFEST.MF b/plugins/org.eclipse.emf.cdo.server/META-INF/MANIFEST.MF index 98aad84a7c..55a6496d9a 100644 --- a/plugins/org.eclipse.emf.cdo.server/META-INF/MANIFEST.MF +++ b/plugins/org.eclipse.emf.cdo.server/META-INF/MANIFEST.MF @@ -1,7 +1,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-SymbolicName: org.eclipse.emf.cdo.server;singleton:=true -Bundle-Version: 4.2.0.qualifier +Bundle-Version: 4.2.1.qualifier Bundle-Name: %pluginName Bundle-Vendor: %providerName Bundle-Localization: plugin @@ -10,18 +10,18 @@ Bundle-Activator: org.eclipse.emf.cdo.internal.server.bundle.OM$Activator Bundle-RequiredExecutionEnvironment: J2SE-1.5 Bundle-ClassPath: . Require-Bundle: org.eclipse.emf.cdo;bundle-version="[4.0.0,5.0.0)";visibility:=reexport -Export-Package: org.eclipse.emf.cdo.internal.server;version="4.2.0"; +Export-Package: org.eclipse.emf.cdo.internal.server;version="4.2.1"; x-friends:="org.eclipse.emf.cdo.server.db, org.eclipse.emf.cdo.server.net4j, org.eclipse.emf.cdo.tests, org.eclipse.emf.cdo.workspace, org.eclipse.emf.cdo.server.hibernate", - org.eclipse.emf.cdo.internal.server.bundle;version="4.2.0";x-internal:=true, - org.eclipse.emf.cdo.internal.server.embedded;version="4.2.0";x-friends:="org.eclipse.emf.cdo.tests", - org.eclipse.emf.cdo.internal.server.mem;version="4.2.0";x-friends:="org.eclipse.emf.cdo.tests", - org.eclipse.emf.cdo.internal.server.messages;version="4.2.0";x-internal:=true, - org.eclipse.emf.cdo.internal.server.syncing;version="4.2.0";x-friends:="org.eclipse.emf.cdo.tests", - org.eclipse.emf.cdo.server;version="4.2.0", - org.eclipse.emf.cdo.server.embedded;version="4.2.0", - org.eclipse.emf.cdo.server.mem;version="4.2.0", - org.eclipse.emf.cdo.spi.server;version="4.2.0" + org.eclipse.emf.cdo.internal.server.bundle;version="4.2.1";x-internal:=true, + org.eclipse.emf.cdo.internal.server.embedded;version="4.2.1";x-friends:="org.eclipse.emf.cdo.tests", + org.eclipse.emf.cdo.internal.server.mem;version="4.2.1";x-friends:="org.eclipse.emf.cdo.tests", + org.eclipse.emf.cdo.internal.server.messages;version="4.2.1";x-internal:=true, + org.eclipse.emf.cdo.internal.server.syncing;version="4.2.1";x-friends:="org.eclipse.emf.cdo.tests", + org.eclipse.emf.cdo.server;version="4.2.1", + org.eclipse.emf.cdo.server.embedded;version="4.2.1", + org.eclipse.emf.cdo.server.mem;version="4.2.1", + org.eclipse.emf.cdo.spi.server;version="4.2.1" diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Session.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Session.java index 14f804b5cd..ab8f065d15 100644 --- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Session.java +++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/Session.java @@ -225,6 +225,7 @@ public class Session extends Container<IView> implements InternalSession this.lockNotificationMode = lockNotificationMode; } + @Deprecated public long getLastUpdateTime() { synchronized (lastUpdateTimeLock) diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/TimeStampAuthority.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/TimeStampAuthority.java index b31401ef8c..6d3c997efb 100644 --- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/TimeStampAuthority.java +++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/TimeStampAuthority.java @@ -33,17 +33,21 @@ class TimeStampAuthority private InternalRepository repository; /** - * Holds the timestamp that was issued in response to the last call to {@link #createTimestamp()} + * Holds the <i>begin</i> timestamp that was issued in response to the last call to {@link #startCommit(long, OMMonitor)}. + * <p> + * Synchronized on <code>TimeStampAuthority.this</code>. */ @ExcludeFromDump private transient long lastIssuedTimeStamp = CDOBranchPoint.UNSPECIFIED_DATE; /** - * Holds the timestamp that was last reported finished by a call to {@link #endCommit(long)} + * Holds the <i>begin</i> timestamp that was last reported finished by a call to {@link #endCommit(long)}. + * <p> + * Synchronized on <code>lastFinishedTimeStampLock</code>. */ private long lastFinishedTimeStamp; - private LastCommitTimeStampLock lastCommitTimeStampLock = new LastCommitTimeStampLock(); + private LastCommitTimeStampLock lastFinishedTimeStampLock = new LastCommitTimeStampLock(); private boolean strictOrdering; // TODO (CD) Should be a repo property @@ -76,11 +80,7 @@ class TimeStampAuthority synchronized long[] startCommit(long timeStampOverride, OMMonitor monitor) { monitor.begin(); - - if (strictOrdering) - { - strictOrderingLock.lock(); - } + lockIfNeeded(); try { @@ -100,10 +100,11 @@ class TimeStampAuthority now = timeStampOverride; } + long previousTimeStamp = lastIssuedTimeStamp; lastIssuedTimeStamp = now; - runningTransactions.add(lastIssuedTimeStamp); - return new long[] { lastIssuedTimeStamp, getLastFinishedTimeStamp() }; + runningTransactions.add(now); + return new long[] { now, previousTimeStamp }; } finally { @@ -124,41 +125,35 @@ class TimeStampAuthority // of the runningTransactions. Since both sets are sorted, we only need to compare the heads. long oldestRunning = runningTransactions.isEmpty() ? Long.MAX_VALUE : runningTransactions.get(0); long oldestFinished; - synchronized (lastCommitTimeStampLock) + synchronized (lastFinishedTimeStampLock) { long oldValue = lastFinishedTimeStamp; while (!finishedTransactions.isEmpty() && (oldestFinished = finishedTransactions.first()) < oldestRunning) { finishedTransactions.remove(oldestFinished); - internalSetLastFinished(oldestFinished); + setLastFinishedTimeStampUnsynced(oldestFinished); } // If we actually changed the lastFinishedTimeStamp, we need to notify waiting threads if (lastFinishedTimeStamp != oldValue) { - lastCommitTimeStampLock.notifyAll(); + lastFinishedTimeStampLock.notifyAll(); } } - if (strictOrdering) - { - strictOrderingLock.unlock(); - } + unlockIfNeeded(); } synchronized void failCommit(long timeStamp) { - if (timeStamp != CDOBranchPoint.UNSPECIFIED_DATE) // Exclude problems before TransactionCommitContext.setTimeStamp() + // Exclude problems before TransactionCommitContext.setTimeStamp() + if (timeStamp == CDOBranchPoint.UNSPECIFIED_DATE) { - if (!runningTransactions.remove(timeStamp)) - { - throw new IllegalArgumentException("Cannot fail transaction with unknown timestamp " + timeStamp); - } + unlockIfNeeded(); } - - if (strictOrdering) + else { - strictOrderingLock.unlock(); + endCommit(timeStamp); } } @@ -183,11 +178,11 @@ class TimeStampAuthority long waitForCommit(long timeout) { - synchronized (lastCommitTimeStampLock) + synchronized (lastFinishedTimeStampLock) { try { - lastCommitTimeStampLock.wait(timeout); + lastFinishedTimeStampLock.wait(timeout); } catch (Exception ignore) { @@ -199,22 +194,39 @@ class TimeStampAuthority void setLastFinishedTimeStamp(long lastCommitTimeStamp) { - synchronized (lastCommitTimeStampLock) + synchronized (lastFinishedTimeStampLock) { - if (lastFinishedTimeStamp < lastCommitTimeStamp) + if (lastCommitTimeStamp > lastFinishedTimeStamp) { - internalSetLastFinished(lastCommitTimeStamp); - lastCommitTimeStampLock.notifyAll(); + lastIssuedTimeStamp = lastCommitTimeStamp; + setLastFinishedTimeStampUnsynced(lastCommitTimeStamp); + lastFinishedTimeStampLock.notifyAll(); } } } - private void internalSetLastFinished(long lastCommitTimeStamp) + private void setLastFinishedTimeStampUnsynced(long lastCommitTimeStamp) { lastFinishedTimeStamp = lastCommitTimeStamp; repository.getStore().setLastCommitTime(lastFinishedTimeStamp); } + private void lockIfNeeded() + { + if (strictOrdering) + { + strictOrderingLock.lock(); + } + } + + private void unlockIfNeeded() + { + if (strictOrdering) + { + strictOrderingLock.unlock(); + } + } + /** * A separate class for better monitor debugging. * diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/server/ISession.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/server/ISession.java index c5c7931d69..38a511338d 100644 --- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/server/ISession.java +++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/server/ISession.java @@ -20,7 +20,7 @@ import org.eclipse.net4j.util.container.IContainer; /** * The server-side representation of a client {@link CDOSession session}. - * + * * @author Eike Stepper * @noextend This interface is not intended to be extended by clients. * @noimplement This interface is not intended to be implemented by clients. @@ -43,7 +43,9 @@ public interface ISession extends CDOCommonSession, IContainer<IView> /** * @since 4.0 + * @deprecated The return value of this method can not be relied upon to be strictly ordered! */ + @Deprecated public long getLastUpdateTime(); /** diff --git a/plugins/org.eclipse.emf.cdo.tests/META-INF/MANIFEST.MF b/plugins/org.eclipse.emf.cdo.tests/META-INF/MANIFEST.MF index 6bf445139b..6be9e09234 100644 --- a/plugins/org.eclipse.emf.cdo.tests/META-INF/MANIFEST.MF +++ b/plugins/org.eclipse.emf.cdo.tests/META-INF/MANIFEST.MF @@ -1,7 +1,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-SymbolicName: org.eclipse.emf.cdo.tests;singleton:=true -Bundle-Version: 4.0.200.qualifier +Bundle-Version: 4.0.201.qualifier Bundle-Name: %pluginName Bundle-Vendor: %providerName Bundle-Localization: plugin @@ -36,28 +36,28 @@ Require-Bundle: org.eclipse.net4j.tests;bundle-version="[4.0.0,5.0.0)";visibilit org.eclipse.emf.cdo.server.admin;bundle-version="[4.1.0,5.0.0)";visibility:=reexport, org.eclipse.gmf.runtime.notation;bundle-version="[1.5.0,2.0.0)";visibility:=reexport, org.eclipse.ocl.ecore;bundle-version="[3.0.0,4.0.0)" -Export-Package: base;version="4.0.200", - base.impl;version="4.0.200", - base.util;version="4.0.200", - derived;version="4.0.200", - derived.impl;version="4.0.200", - derived.util;version="4.0.200", - interface_;version="4.0.200", - interface_.impl;version="4.0.200", - interface_.util;version="4.0.200", - org.eclipse.emf.cdo.tests;version="4.0.200", - org.eclipse.emf.cdo.tests.bugzilla;version="4.0.200", - org.eclipse.emf.cdo.tests.bundle;version="4.0.200";x-internal:=true, - org.eclipse.emf.cdo.tests.config;version="4.0.200", - org.eclipse.emf.cdo.tests.config.impl;version="4.0.200", - org.eclipse.emf.cdo.tests.defs;version="4.0.200", - org.eclipse.emf.cdo.tests.offline;version="4.0.200", - org.eclipse.emf.cdo.tests.performance;version="4.0.200", - org.eclipse.emf.cdo.tests.performance.framework;version="4.0.200", - org.eclipse.emf.cdo.tests.revisioncache;version="4.0.200", - org.eclipse.emf.cdo.tests.util;version="4.0.200", - reference;version="4.0.200", - reference.impl;version="4.0.200"; +Export-Package: base;version="4.0.201", + base.impl;version="4.0.201", + base.util;version="4.0.201", + derived;version="4.0.201", + derived.impl;version="4.0.201", + derived.util;version="4.0.201", + interface_;version="4.0.201", + interface_.impl;version="4.0.201", + interface_.util;version="4.0.201", + org.eclipse.emf.cdo.tests;version="4.0.201", + org.eclipse.emf.cdo.tests.bugzilla;version="4.0.201", + org.eclipse.emf.cdo.tests.bundle;version="4.0.201";x-internal:=true, + org.eclipse.emf.cdo.tests.config;version="4.0.201", + org.eclipse.emf.cdo.tests.config.impl;version="4.0.201", + org.eclipse.emf.cdo.tests.defs;version="4.0.201", + org.eclipse.emf.cdo.tests.offline;version="4.0.201", + org.eclipse.emf.cdo.tests.performance;version="4.0.201", + org.eclipse.emf.cdo.tests.performance.framework;version="4.0.201", + org.eclipse.emf.cdo.tests.revisioncache;version="4.0.201", + org.eclipse.emf.cdo.tests.util;version="4.0.201", + reference;version="4.0.201", + reference.impl;version="4.0.201"; x-friends:="org.eclipse.emf.cdo.dawn.tests, org.eclipse.emf.cdo.tests.db, org.eclipse.emf.cdo.tests.db4o, @@ -66,5 +66,5 @@ Export-Package: base;version="4.0.200", org.eclipse.emf.cdo.tests.objectivity, org.eclipse.emf.cdo.tests.ui, org.eclipse.emf.cdo.tests.mongodb", - reference.util;version="4.0.200" + reference.util;version="4.0.201" Eclipse-BuddyPolicy: dependent diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_338779_Test.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_338779_Test.java index 617404611f..f525ed643c 100644 --- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_338779_Test.java +++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_338779_Test.java @@ -4,7 +4,7 @@ * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html - * + * * Contributors: * Caspar De Groot - initial API and implementation */ @@ -31,10 +31,30 @@ import java.util.Date; */ public class Bugzilla_338779_Test extends AbstractCDOTest { - private void test(PassiveUpdateMode passiveUpdateMode) throws CommitException + public void test_refresh() throws CommitException + { + runTest(null); + } + + public void test_passiveUpdate_invalidations() throws CommitException + { + runTest(PassiveUpdateMode.INVALIDATIONS); + } + + public void test_passiveUpdate_changes() throws CommitException + { + runTest(PassiveUpdateMode.CHANGES); + } + + public void test_passiveUpdate_additions() throws CommitException + { + runTest(PassiveUpdateMode.ADDITIONS); + } + + private void runTest(PassiveUpdateMode passiveUpdateMode) throws CommitException { final CDOSession session = openSession(); - CDOTransaction tx = session.openTransaction(); + CDOTransaction transaction = session.openTransaction(); if (passiveUpdateMode != null) { session.options().setPassiveUpdateEnabled(true); @@ -44,15 +64,16 @@ public class Bugzilla_338779_Test extends AbstractCDOTest { session.options().setPassiveUpdateEnabled(false); } - CDOResource r1 = tx.createResource(getResourcePath("/r1")); //$NON-NLS-1$ - PurchaseOrder po1 = getModel1Factory().createPurchaseOrder(); - po1.setDate(new Date()); - r1.getContents().add(po1); + CDOResource resource1 = transaction.createResource(getResourcePath("/r1")); //$NON-NLS-1$ + + PurchaseOrder purchaseOrder1 = getModel1Factory().createPurchaseOrder(); + purchaseOrder1.setDate(new Date()); + resource1.getContents().add(purchaseOrder1); - tx.commit(); + transaction.commit(); - check(po1, session); + check(purchaseOrder1, session); long timestamp = doSecondSession(); if (passiveUpdateMode != null) @@ -62,7 +83,7 @@ public class Bugzilla_338779_Test extends AbstractCDOTest if (passiveUpdateMode == PassiveUpdateMode.INVALIDATIONS) { // Read something on the object to force load - po1.getDate(); + purchaseOrder1.getDate(); } } else @@ -70,32 +91,12 @@ public class Bugzilla_338779_Test extends AbstractCDOTest session.refresh(); } - check(po1, session); + check(purchaseOrder1, session); - tx.close(); + transaction.close(); session.close(); } - public void test_refresh() throws CommitException - { - test(null); - } - - public void test_passiveUpdate_invalidations() throws CommitException - { - test(PassiveUpdateMode.INVALIDATIONS); - } - - public void test_passiveUpdate_changes() throws CommitException - { - test(PassiveUpdateMode.CHANGES); - } - - public void test_passiveUpdate_additions() throws CommitException - { - test(PassiveUpdateMode.ADDITIONS); - } - private void check(EObject eObject, CDOSession session) { CDOObject obj = CDOUtil.getCDOObject(eObject); @@ -109,15 +110,15 @@ public class Bugzilla_338779_Test extends AbstractCDOTest private long doSecondSession() throws CommitException { CDOSession session = openSession(); - CDOTransaction tx = session.openTransaction(); - CDOResource r1 = tx.getResource(getResourcePath("/r1")); //$NON-NLS-1$ + CDOTransaction transaction = session.openTransaction(); + CDOResource resource1 = transaction.getResource(getResourcePath("/r1")); //$NON-NLS-1$ // Change the purchaseOrder - PurchaseOrder po1 = (PurchaseOrder)r1.getContents().get(0); - po1.setDate(new Date()); + PurchaseOrder purchaseOrder1 = (PurchaseOrder)resource1.getContents().get(0); + purchaseOrder1.setDate(new Date()); - CDOCommitInfo commitInfo = tx.commit(); - tx.close(); + CDOCommitInfo commitInfo = transaction.commit(); + transaction.close(); session.close(); return commitInfo.getTimeStamp(); diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_341995_Test.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_341995_Test.java index 20412b07a4..950078044a 100644 --- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_341995_Test.java +++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_341995_Test.java @@ -4,7 +4,7 @@ * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html - * + * * Contributors: * Caspar De Groot - initial API and implementation */ @@ -25,8 +25,8 @@ import org.eclipse.emf.cdo.util.CommitException; import java.util.concurrent.TimeUnit; /** - * See bug 341995. - * + * Bug 341995: + * * @author Caspar De Groot */ public class Bugzilla_341995_Test extends AbstractCDOTest @@ -34,16 +34,16 @@ public class Bugzilla_341995_Test extends AbstractCDOTest public void test() throws Exception { CDOSession session = openSession(); - CDOTransaction tx = session.openTransaction(); - CDOResource resource = tx.createResource(getResourcePath("test")); + CDOTransaction transaction = session.openTransaction(); + CDOResource resource = transaction.createResource(getResourcePath("test")); Model1Factory factory = getModel1Factory(); - Category cat = factory.createCategory(); - resource.getContents().add(cat); - tx.commit(); + Category category = factory.createCategory(); + resource.getContents().add(category); + transaction.commit(); - CDOObject cdoCat = CDOUtil.getCDOObject(cat); - msg(cdoCat.cdoRevision().getVersion()); + CDOObject cdoCategory = CDOUtil.getCDOObject(category); + msg(cdoCategory.cdoRevision().getVersion()); long delay = 2000L; @@ -60,14 +60,14 @@ public class Bugzilla_341995_Test extends AbstractCDOTest // Attempt the lock; this must block for a while, because it needs to receive // the commitNotification from the commit in the other session, which we are // artificially delaying - cdoCat.cdoWriteLock().lock(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); + cdoCategory.cdoWriteLock().lock(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS); long timeTaken = System.currentTimeMillis() - time1; // We verify that there really was a delay - assertEquals("timeTaken == " + timeTaken, true, timeTaken >= delay); + assertEquals("timeTaken == " + timeTaken, true, timeTaken >= delay - 10); - tx.close(); + transaction.close(); session.close(); } finally @@ -83,17 +83,17 @@ public class Bugzilla_341995_Test extends AbstractCDOTest public void run() { CDOSession session = openSession(); - CDOTransaction tx = session.openTransaction(); - CDOResource resource = tx.getResource(getResourcePath("test")); + CDOTransaction transaction = session.openTransaction(); + CDOResource resource = transaction.getResource(getResourcePath("test")); - Category cat = (Category)resource.getContents().get(0); - cat.setName("dirty"); + Category category = (Category)resource.getContents().get(0); + category.setName("dirty"); CDOCommitInfo info; try { - info = tx.commit(); + info = transaction.commit(); } catch (CommitException ex) { @@ -102,7 +102,7 @@ public class Bugzilla_341995_Test extends AbstractCDOTest msg(info.getTimeStamp()); - tx.close(); + transaction.close(); session.close(); } }; diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_390185_Test.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_390185_Test.java index cb06c5392f..b81aa684e9 100644 --- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_390185_Test.java +++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/bugzilla/Bugzilla_390185_Test.java @@ -15,7 +15,9 @@ import org.eclipse.emf.cdo.session.CDOSession; import org.eclipse.emf.cdo.tests.AbstractCDOTest; import org.eclipse.emf.cdo.tests.model1.Category; import org.eclipse.emf.cdo.transaction.CDOTransaction; -import org.eclipse.emf.cdo.util.CommitException; +import org.eclipse.emf.cdo.util.CommitConflictException; + +import org.eclipse.net4j.util.concurrent.ConcurrencyUtil; import java.util.ArrayList; import java.util.List; @@ -24,17 +26,17 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** - * Bug 390185. + * Bug 390185: Deadlock on multiple concurrent transactions. * * @author Eike Stepper */ public class Bugzilla_390185_Test extends AbstractCDOTest { - private static final int THREADS = 5; + private static final int THREADS = 4; - private static final int TRANSACTIONS_PER_THREAD = 100; + private static final int TRANSACTIONS_PER_THREAD = 40; - public void testIvalidationDeadlock() throws Exception + public void testInvalidationDeadlock() throws Exception { CDOSession session = openSession(); @@ -48,7 +50,7 @@ public class Bugzilla_390185_Test extends AbstractCDOTest if (!latch.await(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)) { - throw new TimeoutException(); + throw new TimeoutException("Not all actory finished in time"); } } @@ -80,6 +82,7 @@ public class Bugzilla_390185_Test extends AbstractCDOTest CDOTransaction transaction = session.openTransaction(); attemptCommit(transaction); transaction.close(); + ConcurrencyUtil.sleep(2); } latch.countDown(); @@ -102,7 +105,7 @@ public class Bugzilla_390185_Test extends AbstractCDOTest break; } } - catch (CommitException ex) + catch (CommitConflictException ex) { transaction.rollback(); } diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/offline/OfflineTest.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/offline/OfflineTest.java index 8dab040d42..e686514ebc 100644 --- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/offline/OfflineTest.java +++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/offline/OfflineTest.java @@ -703,7 +703,7 @@ public class OfflineTest extends AbstractSyncingTest getOfflineConfig().startMasterTransport(); waitForOnline(clone); - sleep(1000); + sleep(3000); IEvent[] sessionEvents = sessionListener.getEvents(); assertEquals(4, sessionEvents.length); // 3x repo state change + 1x invalidation diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOSessionImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOSessionImpl.java index b783e08b74..e20477f37b 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOSessionImpl.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOSessionImpl.java @@ -47,6 +47,7 @@ import org.eclipse.emf.cdo.common.revision.delta.CDOMoveFeatureDelta; import org.eclipse.emf.cdo.common.revision.delta.CDORemoveFeatureDelta; import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta; import org.eclipse.emf.cdo.common.revision.delta.CDOSetFeatureDelta; +import org.eclipse.emf.cdo.common.util.CDOCommonUtil; import org.eclipse.emf.cdo.common.util.CDOException; import org.eclipse.emf.cdo.common.util.RepositoryStateChangedEvent; import org.eclipse.emf.cdo.common.util.RepositoryTypeChangedEvent; @@ -93,7 +94,7 @@ import org.eclipse.net4j.util.WrappedException; import org.eclipse.net4j.util.concurrent.IRWLockManager; import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType; import org.eclipse.net4j.util.concurrent.IRWOLockManager; -import org.eclipse.net4j.util.concurrent.QueueRunner; +import org.eclipse.net4j.util.concurrent.QueueRunner2; import org.eclipse.net4j.util.concurrent.RWOLockManager; import org.eclipse.net4j.util.event.Event; import org.eclipse.net4j.util.event.EventUtil; @@ -105,6 +106,7 @@ import org.eclipse.net4j.util.lifecycle.ILifecycle; import org.eclipse.net4j.util.lifecycle.LifecycleEventAdapter; import org.eclipse.net4j.util.lifecycle.LifecycleUtil; import org.eclipse.net4j.util.om.log.OMLogger; +import org.eclipse.net4j.util.om.trace.ContextTracer; import org.eclipse.net4j.util.options.OptionsEvent; import org.eclipse.net4j.util.security.IPasswordCredentialsProvider; @@ -145,6 +147,8 @@ import java.util.Set; */ public abstract class CDOSessionImpl extends CDOTransactionContainerImpl implements InternalCDOSession { + private static final ContextTracer TRACER = new ContextTracer(OM.DEBUG_SESSION, CDOSessionImpl.class); + private ExceptionHandler exceptionHandler; private CDOIDGenerator idGenerator; @@ -180,9 +184,7 @@ public abstract class CDOSessionImpl extends CDOTransactionContainerImpl impleme private CDOSession.Options options = createOptions(); - private OutOfSequenceInvalidations outOfSequenceInvalidations = new OutOfSequenceInvalidations(); - - private QueueRunner invalidationRunner; + private Invalidator invalidator = new Invalidator(); private CDORepositoryInfo repositoryInfo; @@ -1045,165 +1047,25 @@ public abstract class CDOSessionImpl extends CDOTransactionContainerImpl impleme } } - @Deprecated - public void invalidate(CDOCommitInfo commitInfo, InternalCDOTransaction sender) + public Object startLocalCommit() { - invalidate(commitInfo, sender, true); + return invalidator.startLocalCommit(); } - public void invalidate(CDOCommitInfo commitInfo, InternalCDOTransaction sender, boolean clearResourcePathCache) + public void endLocalCommit(Object token) { - long previousTimeStamp = commitInfo.getPreviousTimeStamp(); - long lastUpdateTime = getLastUpdateTime(); - - if (previousTimeStamp < lastUpdateTime) - { - previousTimeStamp = lastUpdateTime; - } - - synchronized (outOfSequenceInvalidations) - { - outOfSequenceInvalidations.put(previousTimeStamp, new OutOfSequenceInvalidation(commitInfo, sender, - clearResourcePathCache)); - } - - long nextPreviousTimeStamp = lastUpdateTime; - for (;;) - { - Runnable invalidationRunnable = null; - synchronized (outOfSequenceInvalidations) - { - OutOfSequenceInvalidation currentInvalidation = outOfSequenceInvalidations.remove(nextPreviousTimeStamp); - if (currentInvalidation == null) - { - // If we don't have the invalidation that follows the last one we processed, - // then there is nothing we can do right now - break; - } - - final CDOCommitInfo currentCommitInfo = currentInvalidation.getCommitInfo(); - final InternalCDOTransaction currentSender = currentInvalidation.getSender(); - final boolean currentClearResourcePathCache = currentInvalidation.isClearResourcePathCache(); - nextPreviousTimeStamp = currentCommitInfo.getTimeStamp(); - - invalidationRunnable = new Runnable() - { - public void run() - { - invalidateOrdered(currentCommitInfo, currentSender, currentClearResourcePathCache); - } - }; - - if (sender == null) - { - QueueRunner invalidationRunner = getInvalidationRunner(); - invalidationRunner.addWork(invalidationRunnable); - invalidationRunnable = null; - } - } - - if (invalidationRunnable != null) - { - invalidationRunnable.run(); - } - } - } - - /** - * This method is synchronized on outOfSequenceInvalidations by the caller! - */ - private QueueRunner getInvalidationRunner() - { - if (invalidationRunner == null) - { - invalidationRunner = new QueueRunner() - { - @Override - protected void noWork(WorkContext context) - { - if (isClosed()) - { - context.terminate(); - } - } - - @Override - protected String getThreadName() - { - return "CDOSessionInvalidationRunner-" + CDOSessionImpl.this; - } - }; - - invalidationRunner.activate(); - } - - return invalidationRunner; - } - - private void invalidateOrdered(CDOCommitInfo commitInfo, InternalCDOTransaction sender, boolean clearResourcePathCache) - { - Map<CDOID, InternalCDORevision> oldRevisions = null; - boolean success = commitInfo.getBranch() != null; - if (success) - { - oldRevisions = reviseRevisions(commitInfo); - } - - if (options.isPassiveUpdateEnabled()) - { - setLastUpdateTime(commitInfo.getTimeStamp()); - } - - if (success) - { - fireInvalidationEvent(sender, commitInfo); - commitInfoManager.notifyCommitInfoHandlers(commitInfo); - } - - for (InternalCDOView view : getViews()) - { - if (view != sender) - { - invalidateView(commitInfo, view, oldRevisions, clearResourcePathCache); - } - else - { - view.setLastUpdateTime(commitInfo.getTimeStamp()); - } - } + invalidator.endLocalCommit(token); } - private void invalidateView(CDOCommitInfo commitInfo, InternalCDOView view, - Map<CDOID, InternalCDORevision> oldRevisions, boolean clearResourcePathCache) + @Deprecated + public void invalidate(CDOCommitInfo commitInfo, InternalCDOTransaction sender) { - try - { - CDOBranch branch = commitInfo.getBranch(); - long lastUpdateTime = commitInfo.getTimeStamp(); - List<CDORevisionKey> allChangedObjects = commitInfo.getChangedObjects(); - List<CDOIDAndVersion> allDetachedObjects = commitInfo.getDetachedObjects(); - view.invalidate(branch, lastUpdateTime, allChangedObjects, allDetachedObjects, oldRevisions, true, - clearResourcePathCache); - } - catch (RuntimeException ex) - { - if (view.isActive()) - { - OM.LOG.error(ex); - } - else - { - OM.LOG.info(Messages.getString("CDOSessionImpl.1")); //$NON-NLS-1$ - } - } + invalidate(commitInfo, sender, true); } - /** - * @since 2.0 - */ - public void fireInvalidationEvent(InternalCDOTransaction sender, CDOCommitInfo commitInfo) + public void invalidate(CDOCommitInfo commitInfo, InternalCDOTransaction sender, boolean clearResourcePathCache) { - fireEvent(new InvalidationEvent(sender, commitInfo)); + invalidator.reorderInvalidations(commitInfo, sender, clearResourcePathCache); } public Object getAdapter(@SuppressWarnings("rawtypes") Class adapter) @@ -1393,6 +1255,7 @@ public abstract class CDOSessionImpl extends CDOTransactionContainerImpl impleme protected void doActivate() throws Exception { super.doActivate(); + LifecycleUtil.activate(invalidator); InternalCDORemoteSessionManager remoteSessionManager = new CDORemoteSessionManagerImpl(); remoteSessionManager.setLocalSession(this); @@ -1406,11 +1269,9 @@ public abstract class CDOSessionImpl extends CDOTransactionContainerImpl impleme @Override protected void doDeactivate() throws Exception { + LifecycleUtil.deactivate(invalidator); super.doDeactivate(); - LifecycleUtil.deactivate(invalidationRunner); - outOfSequenceInvalidations.clear(); - unhookSessionProtocol(); CDORemoteSessionManager remoteSessionManager = getRemoteSessionManager(); @@ -1455,49 +1316,6 @@ public abstract class CDOSessionImpl extends CDOTransactionContainerImpl impleme /** * @author Eike Stepper - */ - private static final class OutOfSequenceInvalidation - { - private CDOCommitInfo commitInfo; - - private InternalCDOTransaction sender; - - private boolean clearResourcePathCache; - - public OutOfSequenceInvalidation(CDOCommitInfo commitInfo, InternalCDOTransaction sender, - boolean clearResourcePathCache) - { - this.commitInfo = commitInfo; - this.sender = sender; - this.clearResourcePathCache = clearResourcePathCache; - } - - public CDOCommitInfo getCommitInfo() - { - return commitInfo; - } - - public InternalCDOTransaction getSender() - { - return sender; - } - - public boolean isClearResourcePathCache() - { - return clearResourcePathCache; - } - } - - /** - * @author Eike Stepper - */ - private static final class OutOfSequenceInvalidations extends HashMap<Long, OutOfSequenceInvalidation> - { - private static final long serialVersionUID = 1L; - } - - /** - * @author Eike Stepper * @since 2.0 */ protected class OptionsImpl extends Notifier implements Options @@ -1829,6 +1647,240 @@ public abstract class CDOSessionImpl extends CDOTransactionContainerImpl impleme /** * @author Eike Stepper */ + private class Invalidator extends QueueRunner2<Invalidation> + { + private static final boolean DEBUG = false; + + private final Set<Object> unfinishedLocalCommits = new HashSet<Object>(); + + private final List<Invalidation> reorderQueue = new ArrayList<Invalidation>(); + + public Invalidator() + { + } + + public synchronized Object startLocalCommit() + { + if (!isActive()) + { + return null; + } + + final String threadName = Thread.currentThread().getName(); + Object token = new Object() + { + @Override + public String toString() + { + return threadName; + } + }; + + unfinishedLocalCommits.add(token); + return token; + } + + public synchronized void endLocalCommit(Object token) + { + unfinishedLocalCommits.remove(token); + } + + public synchronized void reorderInvalidations(CDOCommitInfo commitInfo, InternalCDOTransaction sender, + boolean clearResourcePathCache) + { + if (!isActive()) + { + return; + } + + Invalidation invalidation = new Invalidation(commitInfo, sender, clearResourcePathCache); + reorderQueue.add(invalidation); + Collections.sort(reorderQueue); + + if (DEBUG) + { + IOUtil.OUT().println( + CDOSessionImpl.this + " [" + getLastUpdateTime() % 10000 + "] " + commitInfo.getPreviousTimeStamp() % 10000 + + " --> " + commitInfo.getTimeStamp() % 10000 + " reorderQueue=" + reorderQueue + + " unfinishedLocalCommits=" + unfinishedLocalCommits); + } + + scheduleInvalidations(); + } + + public synchronized void scheduleInvalidations() + { + while (!reorderQueue.isEmpty() && canProcess(reorderQueue.get(0))) + { + Invalidation invalidation0 = reorderQueue.remove(0); + addWork(invalidation0); + } + } + + protected boolean canProcess(Invalidation invalidation) + { + if (options().isPassiveUpdateEnabled()) + { + long previousTimeStamp = invalidation.getPreviousTimeStamp(); + long lastUpdateTime = getLastUpdateTime(); + return previousTimeStamp <= lastUpdateTime; // Can be smaller in replication scenarios + } + + return unfinishedLocalCommits.size() == 1; // Ourselves + } + + @Override + protected void noWork(WorkContext context) + { + if (isClosed()) + { + context.terminate(); + } + } + + @Override + protected String getThreadName() + { + return "CDOSessionInvalidator-" + CDOSessionImpl.this; + } + } + + /** + * @author Eike Stepper + */ + private final class Invalidation implements Comparable<Invalidation>, Runnable + { + private final CDOCommitInfo commitInfo; + + private final InternalCDOTransaction sender; + + private final boolean clearResourcePathCache; + + public Invalidation(CDOCommitInfo commitInfo, InternalCDOTransaction sender, boolean clearResourcePathCache) + { + this.commitInfo = commitInfo; + this.sender = sender; + this.clearResourcePathCache = clearResourcePathCache; + } + + public long getTimeStamp() + { + return commitInfo.getTimeStamp(); + } + + public long getPreviousTimeStamp() + { + return commitInfo.getPreviousTimeStamp(); + } + + public int compareTo(Invalidation o) + { + return CDOCommonUtil.compareTimeStamps(getTimeStamp(), o.getTimeStamp()); + } + + @Override + public String toString() + { + return Long.toString(commitInfo.getTimeStamp() % 10000); + } + + public void run() + { + long timeStamp = commitInfo.getTimeStamp(); + + if (Invalidator.DEBUG) + { + IOUtil.OUT().println( + CDOSessionImpl.this + " [" + getLastUpdateTime() % 10000 + "] " + timeStamp % 10000 + " INVALIDATE"); + } + + try + { + Map<CDOID, InternalCDORevision> oldRevisions = null; + boolean success = commitInfo.getBranch() != null; + if (success) + { + oldRevisions = reviseRevisions(commitInfo); + } + + if (options.isPassiveUpdateEnabled()/* || sender != null */) + { + setLastUpdateTime(timeStamp); + } + + if (success) + { + CDOSessionImpl.this.fireEvent(new InvalidationEvent(sender, commitInfo)); + commitInfoManager.notifyCommitInfoHandlers(commitInfo); + } + + for (InternalCDOView view : getViews()) + { + invalidateView(commitInfo, view, sender, oldRevisions, clearResourcePathCache); + } + } + catch (RuntimeException ex) + { + if (isActive()) + { + throw ex; + } + + if (TRACER.isEnabled()) + { + TRACER.trace(Messages.getString("CDOSessionImpl.2")); //$NON-NLS-1$ + } + } + finally + { + // setLastUpdateTimeStamp() is not synchronized with the Invalidator. + // Give the Invalidator another chance to schedule Invalidations. + invalidator.scheduleInvalidations(); + } + } + + private void invalidateView(CDOCommitInfo commitInfo, InternalCDOView view, InternalCDOTransaction sender, + Map<CDOID, InternalCDORevision> oldRevisions, boolean clearResourcePathCache) + { + try + { + long lastUpdateTime = commitInfo.getTimeStamp(); + if (view == sender) + { + // The committing view (sender) is already valid, just the timestamp must be set "in sequence". + // Setting the sender's timestamp synchronously can lead to deadlock + view.invalidate(null, lastUpdateTime, null, null, null, true, false); + } + else + { + CDOBranch branch = commitInfo.getBranch(); + List<CDORevisionKey> allChangedObjects = commitInfo.getChangedObjects(); + List<CDOIDAndVersion> allDetachedObjects = commitInfo.getDetachedObjects(); + + view.invalidate(branch, lastUpdateTime, allChangedObjects, allDetachedObjects, oldRevisions, true, + clearResourcePathCache); + } + } + catch (RuntimeException ex) + { + if (view.isActive()) + { + OM.LOG.error(ex); + } + else + { + if (TRACER.isEnabled()) + { + TRACER.trace(Messages.getString("CDOSessionImpl.1")); //$NON-NLS-1$ + } + } + } + } + } + + /** + * @author Eike Stepper + */ private final class InvalidationEvent extends Event implements CDOSessionInvalidationEvent { private static final long serialVersionUID = 1L; diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java index 21d9f1a5f2..7fb9f44ac1 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/transaction/CDOTransactionImpl.java @@ -112,6 +112,7 @@ import org.eclipse.emf.internal.cdo.object.CDONotificationBuilder; import org.eclipse.emf.internal.cdo.object.CDOObjectMerger; import org.eclipse.emf.internal.cdo.object.CDOObjectWrapper; import org.eclipse.emf.internal.cdo.query.CDOQueryImpl; +import org.eclipse.emf.internal.cdo.session.CDOSessionImpl; import org.eclipse.emf.internal.cdo.util.CommitIntegrityCheck; import org.eclipse.emf.internal.cdo.util.CompletePackageClosure; import org.eclipse.emf.internal.cdo.util.IPackageClosure; @@ -1169,11 +1170,32 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa return new CDOCommitContextImpl(this); } + public/* synchronized */CDOCommitInfo commit() throws CommitException + { + return commit(null); + } + /** * @since 2.0 */ - public synchronized CDOCommitInfo commit(IProgressMonitor progressMonitor) throws CommitException + public/* synchronized */CDOCommitInfo commit(IProgressMonitor progressMonitor) throws CommitException + { + CDOCommitInfo info = commitSynced(progressMonitor); + if (info != null) + { + long timeStamp = info.getTimeStamp(); + waitForUpdate(timeStamp, 10000); + } + + return info; + } + + private synchronized CDOCommitInfo commitSynced(IProgressMonitor progressMonitor) throws DanglingIntegrityException, + CommitException { + Object token = null; + InternalCDOSession session = getSession(); + try { checkActive(); @@ -1182,6 +1204,11 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa throw new LocalCommitConflictException(Messages.getString("CDOTransactionImpl.2")); //$NON-NLS-1$ } + if (session instanceof CDOSessionImpl) + { + token = ((CDOSessionImpl)session).startLocalCommit(); + } + if (progressMonitor == null) { progressMonitor = new NullProgressMonitor(); @@ -1210,15 +1237,15 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa } finally { + if (session instanceof CDOSessionImpl) + { + ((CDOSessionImpl)session).endLocalCommit(token); + } + clearResourcePathCacheIfNecessary(null); } } - public synchronized CDOCommitInfo commit() throws CommitException - { - return commit(null); - } - /** * @since 2.0 */ @@ -3013,7 +3040,7 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa getAdapterManager().committedTransaction(transaction, this); cleanUp(this); - Map<CDOID, CDOID> idMappings = result.getIDMappings(); + IListener[] listeners = getListeners(); if (listeners != null) { @@ -3022,6 +3049,7 @@ public class CDOTransactionImpl extends CDOViewImpl implements InternalCDOTransa fireViewTargetChangedEvent(oldBranch.getHead(), listeners); } + Map<CDOID, CDOID> idMappings = result.getIDMappings(); fireEvent(new FinishedEvent(CDOTransactionFinishedEvent.Type.COMMITTED, idMappings), listeners); } |