summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCaspar De Groot2011-07-22 02:49:40 (EDT)
committerCaspar De Groot2011-07-22 02:49:40 (EDT)
commit1036f1d4827c74af0863403cf35f482f201676b7 (patch)
treeedcb855ae7f3c9a461308a178e0619e77d27239c
parentc25c1dd4e3ec84079ff08955b113e03a97f94ab0 (diff)
downloadcdo-1036f1d4827c74af0863403cf35f482f201676b7.zip
cdo-1036f1d4827c74af0863403cf35f482f201676b7.tar.gz
cdo-1036f1d4827c74af0863403cf35f482f201676b7.tar.bz2
[351793] Enhance LockMgr with write options
https://bugs.eclipse.org/bugs/show_bug.cgi?id=351793
-rw-r--r--plugins/org.eclipse.emf.cdo.common/src/org/eclipse/emf/cdo/common/lock/IDurableLockingManager.java59
-rw-r--r--plugins/org.eclipse.emf.cdo.server/src/org/eclipse/emf/cdo/internal/server/LockManager.java58
-rw-r--r--plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/AbstractLockingTest.java18
-rw-r--r--plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingManagerRestartTransactionTest.java30
-rw-r--r--plugins/org.eclipse.emf.cdo.tests/src/org/eclipse/emf/cdo/tests/LockingManagerTest.java73
-rw-r--r--plugins/org.eclipse.emf.cdo/.settings/.api_filters8
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOLock.java12
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/cdo/CDOObject.java5
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/CDOObjectImpl.java13
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/object/CDOLockImpl.java4
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/object/CDOObjectWrapper.java13
-rw-r--r--plugins/org.eclipse.emf.cdo/src/org/eclipse/emf/internal/cdo/session/CDOSessionImpl.java8
-rw-r--r--plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/IRWLockManager.java7
-rw-r--r--plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/RWLockManager.java8
-rw-r--r--plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/concurrent/RWOLockManager.java621
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 4744357..7d042af 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 ad6c3a7..9322e13 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 71afcc5..5774dc9 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 9445833..15c7839 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 df21458..dca6418 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 3db21c4..43d0cb2 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 5ba2386..7240a6e 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 f224201..16dcd90 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 c1fff7f..bb9a1c2 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 94558a8..851c0a1 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 145b626..f0c3d8f 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 50e9c38..4a39ec7 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 a5ac774..8bd9694 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 b9761f6..529a8fe 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 0000000..48a8643
--- /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;
+ }
+ }
+}