diff options
author | Caspar De Groot | 2011-07-22 06:49:40 +0000 |
---|---|---|
committer | Caspar De Groot | 2011-07-22 06:49:40 +0000 |
commit | 1036f1d4827c74af0863403cf35f482f201676b7 (patch) | |
tree | edcb855ae7f3c9a461308a178e0619e77d27239c | |
parent | c25c1dd4e3ec84079ff08955b113e03a97f94ab0 (diff) | |
download | cdo-1036f1d4827c74af0863403cf35f482f201676b7.tar.gz cdo-1036f1d4827c74af0863403cf35f482f201676b7.tar.xz cdo-1036f1d4827c74af0863403cf35f482f201676b7.zip |
[351793] Enhance LockMgr with write options
https://bugs.eclipse.org/bugs/show_bug.cgi?id=351793
15 files changed, 891 insertions, 46 deletions
diff --git a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/IDurableLockingManager.java b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/IDurableLockingManager.java index 474435797f..7d042af590 100644 --- a/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/IDurableLockingManager.java +++ b/plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/IDurableLockingManager.java @@ -111,7 +111,27 @@ public interface IDurableLockingManager */
public enum LockGrade
{
- NONE(0), READ(1), WRITE(2), READ_WRITE(READ.getValue() | WRITE.getValue());
+ NONE(0), READ(1), WRITE(2), READ_WRITE(READ.getValue() | WRITE.getValue()),
+
+ /**
+ * @since 4.1
+ */
+ OPTION(4),
+
+ /**
+ * @since 4.1
+ */
+ READ_OPTION(READ.getValue() | OPTION.getValue()),
+
+ /**
+ * @since 4.1
+ */
+ WRITE_OPTION(WRITE.getValue() | OPTION.getValue()),
+
+ /**
+ * @since 4.1
+ */
+ READ_WRITE_OPTION(READ.getValue() | WRITE.getValue() | OPTION.getValue());
private final int value;
@@ -135,6 +155,14 @@ public interface IDurableLockingManager return (value & 2) != 0;
}
+ /**
+ * @since 4.1
+ */
+ public boolean isOption()
+ {
+ return (value & 4) != 0;
+ }
+
public LockGrade getUpdated(LockType type, boolean on)
{
int mask = type == LockType.READ ? 1 : 2;
@@ -158,14 +186,31 @@ public interface IDurableLockingManager return WRITE;
}
+ if (type == LockType.OPTION)
+ {
+ return OPTION;
+ }
+
return NONE;
}
+ /**
+ * @deprecated Use {@link #get(boolean, boolean, boolean)}
+ */
+ @Deprecated
public static LockGrade get(boolean read, boolean write)
{
return get((read ? 1 : 0) | (write ? 2 : 0));
}
+ /**
+ * @since 4.1
+ */
+ public static LockGrade get(boolean read, boolean write, boolean option)
+ {
+ return get((read ? 1 : 0) | (write ? 2 : 0) | (option ? 4 : 0));
+ }
+
public static LockGrade get(int value)
{
switch (value)
@@ -182,6 +227,18 @@ public interface IDurableLockingManager case 3:
return READ_WRITE;
+ case 4:
+ return OPTION;
+
+ case 1 | 4:
+ return READ_OPTION;
+
+ case 2 | 4:
+ return WRITE_OPTION;
+
+ case 1 | 2 | 4:
+ return READ_WRITE_OPTION;
+
default:
throw new IllegalArgumentException("Invalid lock grade: " + value);
}
diff --git a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/LockManager.java b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/LockManager.java index ad6c3a7ba1..9322e1337b 100644 --- a/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/LockManager.java +++ b/plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/LockManager.java @@ -8,6 +8,7 @@ * Contributors: * Simon McDuff - initial API and implementation * Eike Stepper - maintenance + * Caspar De Groot - write options */ package org.eclipse.emf.cdo.internal.server; @@ -33,7 +34,7 @@ import org.eclipse.emf.cdo.spi.server.InternalView; import org.eclipse.net4j.util.ImplementationError; import org.eclipse.net4j.util.ReflectUtil.ExcludeFromDump; import org.eclipse.net4j.util.WrappedException; -import org.eclipse.net4j.util.concurrent.RWLockManager; +import org.eclipse.net4j.util.concurrent.RWOLockManager; import org.eclipse.net4j.util.container.ContainerEventAdapter; import org.eclipse.net4j.util.container.IContainer; import org.eclipse.net4j.util.event.IListener; @@ -51,7 +52,7 @@ import java.util.Map.Entry; * @author Simon McDuff * @since 3.0 */ -public class LockManager extends RWLockManager<Object, IView> implements InternalLockManager +public class LockManager extends RWOLockManager<Object, IView> implements InternalLockManager { private InternalRepository repository; @@ -106,10 +107,10 @@ public class LockManager extends RWLockManager<Object, IView> implements Interna this.repository = repository; } - public Object getLockEntryObject(Object key) + public synchronized Object getLockEntryObject(Object key) { - LockEntry<Object, IView> lockEntry = getLockEntry(key); - return lockEntry.getObject(); + LockState<Object, IView> lockState = getObjectToLocksMap().get(key); + return lockState.getLockedObject(); } public Object getLockKey(CDOID id, CDOBranch branch) @@ -122,35 +123,35 @@ public class LockManager extends RWLockManager<Object, IView> implements Interna return id; } - public Map<CDOID, LockGrade> getLocks(final IView view) + public synchronized Map<CDOID, LockGrade> getLocks(final IView view) { final Map<CDOID, LockGrade> result = new HashMap<CDOID, LockGrade>(); - LockEntryHandler<Object, IView> handler = new LockEntryHandler<Object, IView>() + + for (LockState<Object, IView> lockState : getObjectToLocksMap().values()) { - public boolean handleLockEntry(LockEntry<Object, IView> lockEntry) + LockGrade grade = LockGrade.NONE; + if (lockState.hasLock(LockType.READ, view, false)) { - CDOID id = getLockKeyID(lockEntry.getObject()); - LockGrade grade = LockGrade.NONE; - if (lockEntry.isReadLock(view)) - { - grade = grade.getUpdated(LockType.READ, true); - } + grade = grade.getUpdated(LockType.READ, true); + } - if (lockEntry.isWriteLock(view)) - { - grade = grade.getUpdated(LockType.WRITE, true); - } + if (lockState.hasLock(LockType.WRITE, view, false)) + { + grade = grade.getUpdated(LockType.WRITE, true); + } - if (grade != LockGrade.NONE) - { - result.put(id, grade); - } + if (lockState.hasLock(LockType.OPTION, view, false)) + { + grade = grade.getUpdated(LockType.OPTION, true); + } - return true; + if (grade != LockGrade.NONE) + { + CDOID id = getLockKeyID(lockState.getLockedObject()); + result.put(id, grade); } - }; + } - handleLockEntries(view, handler); return result; } @@ -482,6 +483,7 @@ public class LockManager extends RWLockManager<Object, IView> implements Interna Collection<Object> readLocks = new ArrayList<Object>(); Collection<Object> writeLocks = new ArrayList<Object>(); + Collection<Object> writeOptions = new ArrayList<Object>(); for (Entry<CDOID, LockGrade> entry : area.getLocks().entrySet()) { Object key = getLockKey(entry.getKey(), area.getBranch()); @@ -495,12 +497,18 @@ public class LockManager extends RWLockManager<Object, IView> implements Interna { writeLocks.add(key); } + + if (grade.isOption()) + { + writeOptions.add(key); + } } try { lock(LockType.READ, view, readLocks, 1000L); lock(LockType.WRITE, view, writeLocks, 1000L); + lock(LockType.OPTION, view, writeOptions, 1000L); } catch (InterruptedException ex) { diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AbstractLockingTest.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AbstractLockingTest.java index 71afcc5006..5774dc971c 100644 --- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AbstractLockingTest.java +++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AbstractLockingTest.java @@ -46,6 +46,18 @@ public class AbstractLockingTest extends AbstractCDOTest cdoObject.cdoWriteLock().unlock(); } + protected static void writeOption(EObject object) throws InterruptedException + { + CDOObject cdoObject = CDOUtil.getCDOObject(object); + assertEquals(true, cdoObject.cdoWriteOption().tryLock(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)); + } + + protected static void writeUnoption(EObject object) throws InterruptedException + { + CDOObject cdoObject = CDOUtil.getCDOObject(object); + cdoObject.cdoWriteOption().unlock(); + } + protected static void assertReadLock(boolean expected, EObject object) { CDOObject cdoObject = CDOUtil.getCDOObject(object); @@ -57,4 +69,10 @@ public class AbstractLockingTest extends AbstractCDOTest CDOObject cdoObject = CDOUtil.getCDOObject(object); assertEquals(expected, cdoObject.cdoWriteLock().isLocked()); } + + protected static void assertWriteOption(boolean expected, EObject object) + { + CDOObject cdoObject = CDOUtil.getCDOObject(object); + assertEquals(expected, cdoObject.cdoWriteOption().isLocked()); + } } diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingManagerRestartTransactionTest.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingManagerRestartTransactionTest.java index 9445833dbb..15c7839f05 100644 --- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingManagerRestartTransactionTest.java +++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingManagerRestartTransactionTest.java @@ -247,6 +247,36 @@ public class LockingManagerRestartTransactionTest extends AbstractLockingTest assertWriteLock(true, company); } + public void testWriteOptionAfterEnable() throws Exception + { + Company company = getModel1Factory().createCompany(); + resource.getContents().add(company); + transaction.commit(); + + String durableLockingID = transaction.enableDurableLocking(true); + writeOption(company); + + restart(durableLockingID); + + company = (Company)resource.getContents().get(0); + assertWriteOption(true, company); + } + + public void testWriteOptionBeforeEnable() throws Exception + { + Company company = getModel1Factory().createCompany(); + resource.getContents().add(company); + transaction.commit(); + + writeOption(company); + String durableLockingID = transaction.enableDurableLocking(true); + + restart(durableLockingID); + + company = (Company)resource.getContents().get(0); + assertWriteOption(true, company); + } + public void testLockUpgradeAfterEnable() throws Exception { Company company = getModel1Factory().createCompany(); diff --git a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingManagerTest.java b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingManagerTest.java index df21458955..dca6418eac 100644 --- a/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingManagerTest.java +++ b/plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingManagerTest.java @@ -8,6 +8,7 @@ * Contributors: * Simon McDuff - initial API and implementation * Eike Stepper - maintenance + * Caspar De Groot - write options */ package org.eclipse.emf.cdo.tests; @@ -25,7 +26,7 @@ import org.eclipse.emf.cdo.util.LockTimeoutException; import org.eclipse.emf.cdo.util.StaleRevisionLockException; import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType; -import org.eclipse.net4j.util.concurrent.RWLockManager; +import org.eclipse.net4j.util.concurrent.RWOLockManager; import org.eclipse.net4j.util.io.IOUtil; import java.util.Collections; @@ -40,9 +41,73 @@ import java.util.concurrent.TimeUnit; */ public class LockingManagerTest extends AbstractLockingTest { + public void testWriteOptions() throws Exception + { + final RWOLockManager<Integer, Integer> lockingManager = new RWOLockManager<Integer, Integer>(); + + Set<Integer> keys = new HashSet<Integer>(); + keys.add(1); + lockingManager.lock(LockType.OPTION, 1, keys, 1000); + + // (R=Read, W=Write, WO=WriteOption) + // Scenario 1: 1 has WO, 2 requests W -> fail + keys.clear(); + keys.add(1); + + try + { + lockingManager.lock(LockType.WRITE, 2, keys, 1000); // Must fail + fail("Should have thrown an exception"); + } + catch (Exception e) + { + } + + // Scenario 2: 1 has WO, 2 requests R -> succeed + try + { + lockingManager.lock(LockType.READ, 2, keys, 1000); // Must succeed + } + catch (Exception e) + { + fail("Should not have thrown an exception"); + } + + // Scenario 3: 1 has WO, 2 has R, 1 requests W -> fail + try + { + lockingManager.lock(LockType.WRITE, 1, keys, 1000); // Must fail + fail("Should have thrown an exception"); + } + catch (Exception e) + { + } + + // Scenario 4: 1 has WO, 2 has R, 2 requests WO -> fail + try + { + lockingManager.lock(LockType.OPTION, 2, keys, 1000); // Must fail + fail("Should have thrown an exception"); + } + catch (Exception e) + { + } + + // Scenario 5: 1 has WO, 2 has nothing, 2 requests WO -> fail + lockingManager.unlock(LockType.READ, 2, keys); + try + { + lockingManager.lock(LockType.OPTION, 2, keys, 1000); // Must fail + fail("Should have thrown an exception"); + } + catch (Exception e) + { + } + } + public void testBasicUpgradeFromReadToWriteLock() throws Exception { - final RWLockManager<Integer, Integer> lockingManager = new RWLockManager<Integer, Integer>(); + final RWOLockManager<Integer, Integer> lockingManager = new RWOLockManager<Integer, Integer>(); Runnable step1 = new Runnable() { @@ -127,7 +192,7 @@ public class LockingManagerTest extends AbstractLockingTest public void testBasicWrongUnlock() throws Exception { - final RWLockManager<Integer, Integer> lockingManager = new RWLockManager<Integer, Integer>(); + final RWOLockManager<Integer, Integer> lockingManager = new RWOLockManager<Integer, Integer>(); Set<Integer> keys = new HashSet<Integer>(); keys.add(1); lockingManager.lock(LockType.READ, 1, keys, 10000); @@ -163,7 +228,7 @@ public class LockingManagerTest extends AbstractLockingTest start = System.currentTimeMillis(); assertEquals(false, CDOUtil.getCDOObject(company2).cdoWriteLock().tryLock(2, TimeUnit.SECONDS)); - assertEquals(true, System.currentTimeMillis() - start > 2000); + assertEquals(true, System.currentTimeMillis() - start >= 2000); } public void testReadLockByOthers() throws Exception diff --git a/plugins/org.eclipse.emf.cdo/.settings/.api_filters b/plugins/org.eclipse.emf.cdo/.settings/.api_filters index 3db21c43e8..43d0cb251c 100644 --- a/plugins/org.eclipse.emf.cdo/.settings/.api_filters +++ b/plugins/org.eclipse.emf.cdo/.settings/.api_filters @@ -1,5 +1,13 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<component id="org.eclipse.emf.cdo" version="2">
+ <resource path="src/org/eclipse/emf/cdo/CDOObject.java" type="org.eclipse.emf.cdo.CDOObject">
+ <filter id="403804204">
+ <message_arguments>
+ <message_argument value="org.eclipse.emf.cdo.CDOObject"/>
+ <message_argument value="cdoWriteOption()"/>
+ </message_arguments>
+ </filter>
+ </resource>
<resource path="src/org/eclipse/emf/cdo/CDOObjectReference.java" type="org.eclipse.emf.cdo.CDOObjectReference">
<filter id="574619656">
<message_arguments>
diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOLock.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOLock.java index 5ba2386e62..7240a6e573 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOLock.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOLock.java @@ -13,8 +13,8 @@ package org.eclipse.emf.cdo; import org.eclipse.emf.cdo.view.CDOView; +import org.eclipse.net4j.util.concurrent.IRWLockManager; import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType; -import org.eclipse.net4j.util.concurrent.RWLockManager; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -31,15 +31,9 @@ import java.util.concurrent.locks.Lock; */ public interface CDOLock extends Lock { - /** - * TODO Simon: JavaDoc - */ - public static final int WAIT = RWLockManager.WAIT; + public static final int WAIT = IRWLockManager.WAIT; - /** - * TODO Simon: JavaDoc - */ - public static final int NO_WAIT = RWLockManager.NO_WAIT; + public static final int NO_WAIT = IRWLockManager.NO_WAIT; /** * TODO Simon: JavaDoc diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOObject.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOObject.java index f224201ba0..16dcd90fd9 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOObject.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOObject.java @@ -126,6 +126,11 @@ public interface CDOObject extends EObject, CDOWithID public CDOLock cdoWriteLock(); /** + * @since 4.1 + */ + public CDOLock cdoWriteOption(); + + /** * Ensures that the revisions of the contained objects up to the given depth are in the local * {@link CDORevisionManager revision cache}. Subsequent access to the respective contained objects will not lead to * server round-trips after calling this method. diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOObjectImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOObjectImpl.java index c1fff7f383..bb9a1c2023 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOObjectImpl.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOObjectImpl.java @@ -205,6 +205,19 @@ public class CDOObjectImpl extends EStoreEObjectImpl implements InternalCDOObjec return new CDOLockImpl(this, LockType.WRITE); } + /** + * @since 4.1 + */ + public CDOLock cdoWriteOption() + { + if (FSMUtil.isTransient(this) || FSMUtil.isNew(this)) + { + return CDOLockImpl.NOOP; + } + + return new CDOLockImpl(this, LockType.OPTION); + } + public void cdoInternalSetID(CDOID id) { if (TRACER.isEnabled()) diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/object/CDOLockImpl.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/object/CDOLockImpl.java index 94558a885a..851c0a10cf 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/object/CDOLockImpl.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/object/CDOLockImpl.java @@ -65,7 +65,7 @@ public class CDOLockImpl implements CDOLock { try { - object.cdoView().lockObjects(Collections.singletonList(object), type, CDOLock.WAIT); + object.cdoView().lockObjects(Collections.singletonList(object), type, WAIT); } catch (InterruptedException ex) { @@ -112,7 +112,7 @@ public class CDOLockImpl implements CDOLock { try { - object.cdoView().lockObjects(Collections.singletonList(object), type, CDOLock.NO_WAIT); + object.cdoView().lockObjects(Collections.singletonList(object), type, NO_WAIT); return true; } catch (LockTimeoutException ex) diff --git a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/object/CDOObjectWrapper.java b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/object/CDOObjectWrapper.java index 145b6264f5..f0c3d8f120 100644 --- a/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/object/CDOObjectWrapper.java +++ b/plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/object/CDOObjectWrapper.java @@ -198,6 +198,19 @@ public abstract class CDOObjectWrapper implements InternalCDOObject return new CDOLockImpl(this, LockType.WRITE); } + /** + * @since 4.1 + */ + public CDOLock cdoWriteOption() + { + if (FSMUtil.isTransient(this) || FSMUtil.isNew(this)) + { + return CDOLockImpl.NOOP; + } + + return new CDOLockImpl(this, LockType.OPTION); + } + public EList<Adapter> eAdapters() { return instance.eAdapters(); 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 50e9c385dc..4a39ec7d44 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 @@ -89,7 +89,7 @@ import org.eclipse.net4j.util.WrappedException; import org.eclipse.net4j.util.collection.Pair; import org.eclipse.net4j.util.concurrent.IRWLockManager; import org.eclipse.net4j.util.concurrent.IRWLockManager.LockType; -import org.eclipse.net4j.util.concurrent.RWLockManager; +import org.eclipse.net4j.util.concurrent.RWOLockManager; import org.eclipse.net4j.util.event.Event; import org.eclipse.net4j.util.event.EventUtil; import org.eclipse.net4j.util.event.IEvent; @@ -183,7 +183,7 @@ public abstract class CDOSessionImpl extends CDOTransactionContainerImpl impleme } }; - private IRWLockManager<CDOSessionImpl, Object> lockmanager = new RWLockManager<CDOSessionImpl, Object>(); + private IRWLockManager<CDOSessionImpl, Object> lockManager = new RWOLockManager<CDOSessionImpl, Object>(); @ExcludeFromDump private Set<CDOSessionImpl> singletonCollection = Collections.singleton(this); @@ -530,7 +530,7 @@ public abstract class CDOSessionImpl extends CDOTransactionContainerImpl impleme { try { - lockmanager.lock(LockType.WRITE, key, this, RWLockManager.WAIT); + lockManager.lock(LockType.WRITE, key, this, IRWLockManager.WAIT); } catch (InterruptedException ex) { @@ -540,7 +540,7 @@ public abstract class CDOSessionImpl extends CDOTransactionContainerImpl impleme public void releaseAtomicRequestLock(Object key) { - lockmanager.unlock(LockType.WRITE, key, singletonCollection); + lockManager.unlock(LockType.WRITE, key, singletonCollection); } @Override diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/IRWLockManager.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/IRWLockManager.java index a5ac774f8d..8bd9694ef3 100644 --- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/IRWLockManager.java +++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/IRWLockManager.java @@ -53,6 +53,11 @@ public interface IRWLockManager<OBJECT, CONTEXT> */ public static enum LockType { - WRITE, READ + WRITE, READ, + + /** + * @since 3.2 + */ + OPTION } } diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/RWLockManager.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/RWLockManager.java index b9761f6b4e..529a8fee40 100644 --- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/RWLockManager.java +++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/RWLockManager.java @@ -32,7 +32,9 @@ import java.util.Set; * * @author Simon McDuff * @since 2.0 + * @deprecated Use {@link RWOLockManager} */ +@Deprecated public class RWLockManager<OBJECT, CONTEXT> extends Lifecycle implements IRWLockManager<OBJECT, CONTEXT> { private LockStrategy<OBJECT, CONTEXT> readLockStrategy = new LockStrategy<OBJECT, CONTEXT>() @@ -385,7 +387,9 @@ public class RWLockManager<OBJECT, CONTEXT> extends Lifecycle implements IRWLock /** * @author Simon McDuff * @since 3.1 + * @deprecated Use {@link RWOLockManager} */ + @Deprecated protected interface LockStrategy<OBJECT, CONTEXT> { public boolean isLocked(LockEntry<OBJECT, CONTEXT> entry, CONTEXT context); @@ -402,7 +406,9 @@ public class RWLockManager<OBJECT, CONTEXT> extends Lifecycle implements IRWLock /** * @author Simon McDuff * @since 3.1 + * @deprecated Use {@link RWOLockManager} */ + @Deprecated protected interface LockEntry<OBJECT, CONTEXT> { public OBJECT getObject(); @@ -443,7 +449,9 @@ public class RWLockManager<OBJECT, CONTEXT> extends Lifecycle implements IRWLock /** * @author Eike Stepper * @since 3.1 + * @deprecated Use {@link RWOLockManager} */ + @Deprecated protected interface LockEntryHandler<OBJECT, CONTEXT> { public boolean handleLockEntry(LockEntry<OBJECT, CONTEXT> lockEntry); diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/RWOLockManager.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/RWOLockManager.java new file mode 100644 index 0000000000..48a8643252 --- /dev/null +++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/RWOLockManager.java @@ -0,0 +1,621 @@ +/** + * Copyright (c) 2004 - 2011 Eike Stepper (Berlin, Germany) and others. + * All rights reserved. This program and the accompanying materials + * 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 + */ +package org.eclipse.net4j.util.concurrent; + +import org.eclipse.net4j.util.CheckUtil; +import org.eclipse.net4j.util.ObjectUtil; +import org.eclipse.net4j.util.collection.HashBag; +import org.eclipse.net4j.util.lifecycle.Lifecycle; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author Caspar De Groot + * @since 3.2 + */ +public class RWOLockManager<OBJECT, CONTEXT> extends Lifecycle implements IRWLockManager<OBJECT, CONTEXT> +{ + // TODO (CD) Ensure that CDOID and CDOIDandBranch have good hashCode implementations + private final Map<OBJECT, LockState<OBJECT, CONTEXT>> objectToLocksMap = createObjectToLocksMap(); + + // TODO (CD) Ensure that IView has a good hashCode implementation + private final Map<CONTEXT, Set<LockState<OBJECT, CONTEXT>>> contextToLocksMap = createContextToLocksMap(); + + public void lock(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToLock, long timeout) + throws InterruptedException + { + if (objectsToLock.isEmpty()) + { + return; + } + + // Must come before the synchronized block! + long startTime = timeout == WAIT ? 0L : currentTimeMillis(); + + // Do not synchronize the entire method as it would corrupt the timeout! + synchronized (this) + { + int count = objectsToLock.size(); + LockState<?, ?>[] lockStates = new LockState<?, ?>[count]; + + for (;;) + { + if (canLockInContext(type, context, objectsToLock, lockStates)) + { + for (int i = 0; i < count; i++) + { + @SuppressWarnings("unchecked") + LockState<OBJECT, CONTEXT> lockState = (LockState<OBJECT, CONTEXT>)lockStates[i]; + lockState.lock(type, context); + addLockToContext(context, lockState); + } + + return; + } + + wait(startTime, timeout); + } + } + } + + public void lock(LockType type, CONTEXT context, OBJECT objectToLock, long timeout) throws InterruptedException + { + // Do not synchronize the entire method as it would corrupt the timeout! + lock(type, context, Collections.singleton(objectToLock), timeout); + } + + public synchronized void unlock(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToUnlock) + { + if (objectsToUnlock.isEmpty()) + { + return; + } + + List<LockState<OBJECT, CONTEXT>> lockStates = new LinkedList<LockState<OBJECT, CONTEXT>>(); + + Iterator<? extends OBJECT> it = objectsToUnlock.iterator(); + while (it.hasNext()) + { + OBJECT o = it.next(); + LockState<OBJECT, CONTEXT> lockState = objectToLocksMap.get(o); + if (lockState == null || !lockState.canUnlock(type, context)) + { + throw new IllegalMonitorStateException(); + } + + lockStates.add(lockState); + } + + for (LockState<OBJECT, CONTEXT> lockState : lockStates) + { + lockState.unlock(type, context); + if (!lockState.hasLocks(context)) + { + removeLockFromContext(context, lockState); + } + + if (lockState.hasNoLocks()) + { + objectToLocksMap.remove(lockState.getLockedObject()); + } + } + + notifyAll(); + } + + public synchronized void unlock(CONTEXT context) + { + Set<LockState<OBJECT, CONTEXT>> lockStates = contextToLocksMap.get(context); + if (lockStates == null) + { + return; + } + + List<OBJECT> objectsWithoutLocks = new LinkedList<OBJECT>(); + + for (LockState<OBJECT, CONTEXT> lockState : lockStates) + { + for (LockType lockType : LockType.values()) + { + if (lockState.hasLock(lockType, context, false)) + { + lockState.unlock(lockType, context); + } + + // TODO (CD) Consider whether WRITE_OPTIONs should be excluded from this... + } + + removeLockFromContext(context, lockState); + if (lockState.hasNoLocks()) + { + OBJECT o = lockState.getLockedObject(); + objectsWithoutLocks.add(o); + } + } + + // This must be done outside the above iteration, in order to avoid ConcurrentModEx + for (OBJECT o : objectsWithoutLocks) + { + objectToLocksMap.remove(o); + } + + notifyAll(); + } + + public synchronized boolean hasLock(LockType type, CONTEXT context, OBJECT objectToLock) + { + // TODO (CD) Should this be synced? + LockState<OBJECT, CONTEXT> lockState = objectToLocksMap.get(objectToLock); + return lockState != null && lockState.hasLock(type, context, false); + } + + public synchronized boolean hasLockByOthers(LockType type, CONTEXT context, OBJECT objectToLock) + { + // TODO (CD) Should this be synced? + LockState<OBJECT, CONTEXT> lockState = objectToLocksMap.get(objectToLock); + return lockState != null && lockState.hasLock(type, context, true); + } + + protected synchronized void changeContext(CONTEXT oldContext, CONTEXT newContext) + { + for (LockState<OBJECT, CONTEXT> lockState : objectToLocksMap.values()) + { + lockState.replaceContext(oldContext, newContext); + } + } + + protected long currentTimeMillis() + { + return System.currentTimeMillis(); + } + + protected Map<OBJECT, LockState<OBJECT, CONTEXT>> createObjectToLocksMap() + { + return new HashMap<OBJECT, LockState<OBJECT, CONTEXT>>(); + } + + protected Map<CONTEXT, Set<LockState<OBJECT, CONTEXT>>> createContextToLocksMap() + { + return new HashMap<CONTEXT, Set<LockState<OBJECT, CONTEXT>>>(); + } + + /** + * All access to the returned map must be properly synchronized on this {@link RWOLockManager}. + */ + protected final Map<OBJECT, LockState<OBJECT, CONTEXT>> getObjectToLocksMap() + { + return objectToLocksMap; + } + + /** + * All access to the returned map must be properly synchronized on this {@link RWOLockManager}. + */ + protected final Map<CONTEXT, Set<LockState<OBJECT, CONTEXT>>> getContextToLocksMap() + { + return contextToLocksMap; + } + + private LockState<OBJECT, CONTEXT> getOrCreateLockState(OBJECT o) + { + LockState<OBJECT, CONTEXT> lockState = objectToLocksMap.get(o); + if (lockState == null) + { + lockState = new LockState<OBJECT, CONTEXT>(o); + objectToLocksMap.put(o, lockState); + } + + return lockState; + } + + private boolean canLockInContext(LockType type, CONTEXT context, Collection<? extends OBJECT> objectsToLock, + LockState<?, ?>[] lockStatesToFill) + { + Iterator<? extends OBJECT> it = objectsToLock.iterator(); + for (int i = 0; i < lockStatesToFill.length; i++) + { + OBJECT o = it.next(); + LockState<OBJECT, CONTEXT> lockState = getOrCreateLockState(o); + if (!lockState.canLock(type, context)) + { + return false; + } + + lockStatesToFill[i] = lockState; + } + + return true; + } + + private void addLockToContext(CONTEXT context, LockState<OBJECT, CONTEXT> lockState) + { + Set<LockState<OBJECT, CONTEXT>> lockStates = contextToLocksMap.get(context); + if (lockStates == null) + { + lockStates = new HashSet<LockState<OBJECT, CONTEXT>>(); + contextToLocksMap.put(context, lockStates); + } + + lockStates.add(lockState); + } + + private void removeLockFromContext(CONTEXT context, LockState<OBJECT, CONTEXT> lockState) + { + Set<LockState<OBJECT, CONTEXT>> lockStates = contextToLocksMap.get(context); + lockStates.remove(lockState); + if (lockStates.isEmpty()) + { + contextToLocksMap.remove(context); + } + } + + private void wait(long startTime, long timeout) throws InterruptedException + { + if (timeout == WAIT) + { + wait(); + } + else + { + long elapsedTime = currentTimeMillis() - startTime; + long waitTime = timeout - elapsedTime; + if (waitTime < 1) + { + throw new TimeoutRuntimeException("Could not lock objects within " + timeout + " milli seconds"); + } + + wait(waitTime); + } + } + + /** + * Represents a combination of locks for one OBJECT. + * + * @author Caspar De Groot + * @since 3.2 + */ + protected static class LockState<OBJECT, CONTEXT> + { + private final OBJECT lockedObject; + + // TODO (CD) Ensure that IView has a good hashCode implementation + private final HashBag<CONTEXT> readLockOwners = new HashBag<CONTEXT>(); + + private CONTEXT writeLockOwner; + + private CONTEXT writeOptionOwner; + + private int writeLockCounter; + + LockState(OBJECT lockedObject) + { + CheckUtil.checkArg(lockedObject, "lockedObject"); + this.lockedObject = lockedObject; + } + + public OBJECT getLockedObject() + { + return lockedObject; + } + + public boolean hasLock(org.eclipse.net4j.util.concurrent.IRWLockManager.LockType type, CONTEXT view, + boolean byOthers) + { + CheckUtil.checkArg(view, "view"); + + switch (type) + { + case READ: + if (byOthers) + { + return readLockOwners.size() > 1 || readLockOwners.size() == 1 && !readLockOwners.contains(view); + } + + return readLockOwners.contains(view); + + case WRITE: + if (byOthers) + { + return writeLockOwner != null && writeLockOwner != view; + } + + return writeLockOwner == view; + + case OPTION: + if (byOthers) + { + return writeOptionOwner != null && writeOptionOwner != view; + } + + return writeOptionOwner == view; + } + + return false; + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder("LockState[target="); + builder.append(lockedObject); + + if (readLockOwners.size() > 0) + { + builder.append(", read="); + boolean first = true; + for (CONTEXT view : readLockOwners) + { + if (first) + { + first = false; + } + else + { + builder.append(", "); + } + + builder.append(view); + } + + builder.deleteCharAt(builder.length() - 1); + } + + if (writeLockOwner != null) + { + builder.append(", write="); + builder.append(writeLockOwner); + } + + if (writeOptionOwner != null) + { + builder.append(", option="); + builder.append(writeOptionOwner); + } + + builder.append(']'); + return builder.toString(); + } + + void lock(LockType type, CONTEXT context) + { + CheckUtil.checkArg(context, "context"); + switch (type) + { + case READ: + doReadLock(context); + return; + + case WRITE: + doWriteLock(context); + return; + + case OPTION: + doWriteOption(context); + return; + } + + throw new AssertionError("Unknown lock type " + type); + } + + boolean canLock(LockType type, CONTEXT context) + { + CheckUtil.checkArg(context, "context"); + switch (type) + { + case READ: + return canReadLock(context); + + case WRITE: + return canWriteLock(context); + + case OPTION: + return canWriteOption(context); + } + + throw new AssertionError("Unknown lock type " + type); + } + + boolean canUnlock(LockType type, CONTEXT context) + { + CheckUtil.checkArg(context, "context"); + switch (type) + { + case READ: + return canReadUnlock(context); + + case WRITE: + return canWriteUnlock(context); + + case OPTION: + return canWriteUnoption(context); + } + + throw new AssertionError("Unknown lock type " + type); + } + + void unlock(LockType type, CONTEXT context) + { + CheckUtil.checkArg(context, "context"); + switch (type) + { + case READ: + doReadUnlock(context); + return; + + case WRITE: + doWriteUnlock(context); + return; + + case OPTION: + doWriteUnoption(context); + return; + } + + throw new AssertionError("Unknown lock type " + type); + } + + void replaceContext(CONTEXT oldContext, CONTEXT newContext) + { + int readLocksOwnedByOldView = readLockOwners.getCounterFor(oldContext); + if (readLocksOwnedByOldView > 0) + { + for (int i = 0; i < readLocksOwnedByOldView; i++) + { + readLockOwners.remove(oldContext); + readLockOwners.add(newContext); + } + } + + if (ObjectUtil.equals(writeLockOwner, oldContext)) + { + writeLockOwner = newContext; + } + + if (ObjectUtil.equals(writeOptionOwner, oldContext)) + { + writeOptionOwner = newContext; + } + } + + boolean hasNoLocks() + { + return readLockOwners.isEmpty() && writeLockOwner == null && writeOptionOwner == null; + } + + boolean hasLocks(CONTEXT context) + { + return readLockOwners.contains(context) || writeLockOwner == context || writeOptionOwner == context; + } + + private boolean canReadLock(CONTEXT context) + { + if (writeLockOwner != null && writeLockOwner != context) + { + return false; + } + + return true; + } + + private void doReadLock(CONTEXT context) + { + readLockOwners.add(context); + } + + private boolean canWriteLock(CONTEXT context) + { + // If another context owns a writeLock, we can't write-lock + if (writeLockOwner != null && writeLockOwner != context) + { + return false; + } + + // If another context owns a writeOption, we can't write-lock + if (writeOptionOwner != null && writeOptionOwner != context) + { + return false; + } + + // If another context owns a readLock, we can't write-lock + if (readLockOwners.size() > 1) + { + return false; + } + + if (readLockOwners.size() == 1) + { + if (!readLockOwners.contains(context)) + { + return false; + } + } + + return true; + } + + private void doWriteLock(CONTEXT context) + { + writeLockOwner = context; + writeLockCounter++; + } + + private boolean canWriteOption(CONTEXT context) + { + if (writeOptionOwner != null && writeOptionOwner != context) + { + return false; + } + + return true; + } + + private void doWriteOption(CONTEXT context) + { + writeOptionOwner = context; + } + + private boolean canReadUnlock(CONTEXT context) + { + if (!readLockOwners.contains(context)) + { + return false; + } + + return true; + } + + private void doReadUnlock(CONTEXT context) + { + readLockOwners.remove(context); + } + + private boolean canWriteUnlock(CONTEXT context) + { + if (writeLockOwner != context) + { + return false; + } + + return true; + } + + private void doWriteUnlock(CONTEXT context) + { + writeLockCounter--; + if (writeLockCounter == 0) + { + writeLockOwner = null; + } + } + + private boolean canWriteUnoption(CONTEXT context) + { + if (writeOptionOwner != context) + { + return false; + } + + return true; + } + + private void doWriteUnoption(CONTEXT context) + { + writeOptionOwner = null; + } + } +} |