Author: manik.surtani(a)jboss.com
Date: 2008-01-04 14:03:15 -0500 (Fri, 04 Jan 2008)
New Revision: 4993
Modified:
core/trunk/src/main/java/org/jboss/cache/interceptors/PessimisticLockInterceptor.java
core/trunk/src/main/java/org/jboss/cache/invocation/RemoteCacheInvocationDelegate.java
core/trunk/src/test/java/org/jboss/cache/api/CacheAPIOptimisticTest.java
core/trunk/src/test/java/org/jboss/cache/api/CacheAPITest.java
core/trunk/src/test/java/org/jboss/cache/buddyreplication/GravitationCleanupTest.java
Log:
JBCACHE-1256 - PessimisticLockInterceptor does not clean up all temporary nodes created
during a removeNode() call
Modified:
core/trunk/src/main/java/org/jboss/cache/interceptors/PessimisticLockInterceptor.java
===================================================================
---
core/trunk/src/main/java/org/jboss/cache/interceptors/PessimisticLockInterceptor.java 2008-01-04
18:52:20 UTC (rev 4992)
+++
core/trunk/src/main/java/org/jboss/cache/interceptors/PessimisticLockInterceptor.java 2008-01-04
19:03:15 UTC (rev 4993)
@@ -118,7 +118,7 @@
}
else
{
- acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.WRITE, true, false, false,
true);
+ acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.WRITE, true, false, false,
true, null, false);
}
return nextInterceptor(ctx);
}
@@ -130,7 +130,7 @@
protected Object handleLockMethod(InvocationContext ctx, Fqn fqn, NodeLock.LockType
lockType, boolean recursive) throws Throwable
{
- acquireLocksWithTimeout(ctx, fqn, lockType, false, false, false, false);
+ acquireLocksWithTimeout(ctx, fqn, lockType, false, false, false, false, null,
false);
if (recursive)
{
//acquireLocksOnChildren(cache.peek(fqn, false), lockType, ctx);
@@ -204,7 +204,7 @@
if (trace) log.trace("Attempting to get WL on node to be moved [" + from
+ "]");
if (from != null && !(configuration.getIsolationLevel() ==
IsolationLevel.NONE))
{
- lock(ctx, from, NodeLock.LockType.WRITE, false, timeout, true, false);
+ lock(ctx, from, NodeLock.LockType.WRITE, false, timeout, true, false, null,
false);
if (ctx.getGlobalTransaction() != null)
{
cache.getTransactionTable().get(ctx.getGlobalTransaction()).addRemovedNode(from);
@@ -215,7 +215,7 @@
{
//now for an RL for the new parent.
if (trace) log.trace("Attempting to get RL on new parent [" + to +
"]");
- lock(ctx, to, NodeLock.LockType.READ, false, timeout, false, false);
+ lock(ctx, to, NodeLock.LockType.READ, false, timeout, false, false, null,
false);
acquireLocksOnChildren(peekNode(ctx, to, false, true, false),
NodeLock.LockType.READ, ctx);
}
Object retValue = nextInterceptor(ctx);
@@ -230,16 +230,27 @@
protected Object handleRemoveNodeMethod(InvocationContext ctx, GlobalTransaction tx,
Fqn fqn, boolean createUndoOps) throws Throwable
{
- boolean created = acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.WRITE, true,
false, true, false);
+ // need to make a note of ALL nodes created here!!
+ List<Fqn> createdNodes = new LinkedList<Fqn>();
+ // we need to mark new nodes created as deleted since they are only created to form
a path to the node being removed, to
+ // create a lock.
+ boolean created = acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.WRITE, true,
false, true, false, createdNodes, true);
if (ctx.getGlobalTransaction() != null)
{
-
cache.getTransactionTable().get(ctx.getGlobalTransaction()).addRemovedNode(fqn);
+ TransactionEntry entry = tx_table.get(ctx.getGlobalTransaction());
+ entry.addRemovedNode(fqn);
+ for (Fqn f : createdNodes) entry.addRemovedNode(f);
}
- acquireLocksOnChildren(rootNode.getChildDirect(fqn), NodeLock.LockType.WRITE,
ctx);
+ acquireLocksOnChildren(peekNode(ctx, fqn, false, false, false),
NodeLock.LockType.WRITE, ctx);
Object retVal = nextInterceptor(ctx);
+
+ // and make sure we remove all nodes we've created for the sake of later
removal.
if (ctx.getGlobalTransaction() == null)
{
+
+ for (Fqn f : createdNodes) cacheImpl.realRemove(f, true);
cacheImpl.realRemove(fqn, true);
+
NodeSPI n = peekNode(ctx, fqn, false, true, false);
if (n != null)
{
@@ -252,7 +263,7 @@
protected Object handlePutForExternalReadMethod(InvocationContext ctx,
GlobalTransaction tx, Fqn fqn, Object key, Object value) throws Throwable
{
- acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.READ, true, true, false,
true);
+ acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.READ, true, true, false, true,
null, false);
return nextInterceptor(ctx);
}
@@ -263,61 +274,61 @@
protected Object handleRemoveDataMethod(InvocationContext ctx, GlobalTransaction tx,
Fqn fqn, boolean createUndoOps) throws Throwable
{
- acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.WRITE, false, false, false,
false);
+ acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.WRITE, false, false, false,
false, null, false);
return nextInterceptor(ctx);
}
protected Object handleAddChildMethod(InvocationContext ctx, GlobalTransaction tx, Fqn
parentFqn, Object childName, Node cn, boolean createUndoOps) throws Throwable
{
- acquireLocksWithTimeout(ctx, parentFqn, NodeLock.LockType.READ, false, false,
false, false);
+ acquireLocksWithTimeout(ctx, parentFqn, NodeLock.LockType.READ, false, false,
false, false, null, false);
return nextInterceptor(ctx);
}
protected Object handleEvictMethod(InvocationContext ctx, Fqn fqn) throws Throwable
{
- acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.WRITE, false, true, false,
false);
+ acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.WRITE, false, true, false,
false, null, false);
return nextInterceptor(ctx);
}
protected Object handleGetKeyValueMethod(InvocationContext ctx, Fqn fqn, Object key,
boolean sendNodeEvent) throws Throwable
{
- acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.READ, false, false, false,
false);
+ acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.READ, false, false, false,
false, null, false);
return nextInterceptor(ctx);
}
protected Object handleGetNodeMethod(InvocationContext ctx, Fqn fqn) throws Throwable
{
- acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.READ, false, false, false,
false);
+ acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.READ, false, false, false,
false, null, false);
return nextInterceptor(ctx);
}
protected Object handleGetKeysMethod(InvocationContext ctx, Fqn fqn) throws Throwable
{
- acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.READ, false, false, false,
false);
+ acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.READ, false, false, false,
false, null, false);
return nextInterceptor(ctx);
}
protected Object handleGetChildrenNamesMethod(InvocationContext ctx, Fqn fqn) throws
Throwable
{
- acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.READ, false, false, false,
false);
+ acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.READ, false, false, false,
false, null, false);
return nextInterceptor(ctx);
}
protected Object handlePrintMethod(InvocationContext ctx, Fqn fqn) throws Throwable
{
- acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.READ, false, false, false,
false);
+ acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.READ, false, false, false,
false, null, false);
return nextInterceptor(ctx);
}
protected Object handleReleaseAllLocksMethod(InvocationContext ctx, Fqn fqn) throws
Throwable
{
- acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.READ, false, false, false,
false);
+ acquireLocksWithTimeout(ctx, fqn, NodeLock.LockType.READ, false, false, false,
false, null, false);
return nextInterceptor(ctx);
}
private boolean acquireLocksWithTimeout(InvocationContext ctx, Fqn fqn,
NodeLock.LockType lockType,
boolean createIfNotExists, boolean
zeroLockTimeout,
- boolean acquireLockOnParent, boolean
reverseRemoveCheck)
+ boolean acquireLockOnParent, boolean
reverseRemoveCheck, List<Fqn> createdNodes, boolean markNewNodesAsDeleted)
throws InterruptedException
{
if (fqn == null || configuration.getIsolationLevel() == IsolationLevel.NONE) return
false;
@@ -334,7 +345,7 @@
{
throw new TimeoutException("Unable to acquire lock on Fqn " + fqn +
" after " + timeout + " millis");
}
- created = lock(ctx, fqn, lockType, createIfNotExists, timeout,
acquireLockOnParent, reverseRemoveCheck);
+ created = lock(ctx, fqn, lockType, createIfNotExists, timeout,
acquireLockOnParent, reverseRemoveCheck, createdNodes, markNewNodesAsDeleted);
firstTry = false;
}
while (createIfNotExists && peekNode(ctx, fqn, false, true, false) ==
null);// keep trying until we have the lock (fixes concurrent remove())
@@ -347,12 +358,14 @@
* 2) acquireWriteLockOnParent is true. If so AND {@link
org.jboss.cache.Node#isLockForChildInsertRemove()} then a read
* lock will be aquired for the parent of the node.
*
- * @param createIfNotExists if true, then missing nodes will be cretaed on the fly.
If false, method returns if we
- * reach a node that does not exists
- * @param reverseRemoveCheck see {@link
#manageReverseRemove(org.jboss.cache.transaction.GlobalTransaction,
org.jboss.cache.NodeSPI, boolean)}
+ * @param createIfNotExists if true, then missing nodes will be cretaed on the
fly. If false, method returns if we
+ * reach a node that does not exists
+ * @param reverseRemoveCheck see {@link
#manageReverseRemove(org.jboss.cache.transaction.GlobalTransaction,
org.jboss.cache.NodeSPI, boolean)}
+ * @param createdNodes a list to which any nodes created can register their
Fqns so that calling code is aware of which nodes have been newly created.
+ * @param markNewNodesAsDeleted
*/
private boolean lock(InvocationContext ctx, Fqn fqn, NodeLock.LockType lockType,
boolean createIfNotExists, long timeout,
- boolean acquireWriteLockOnParent, boolean reverseRemoveCheck)
+ boolean acquireWriteLockOnParent, boolean reverseRemoveCheck,
List<Fqn> createdNodes, boolean markNewNodesAsDeleted)
throws TimeoutException, LockingException, InterruptedException
{
Thread currentThread = Thread.currentThread();
@@ -380,6 +393,8 @@
currentNode = parent.addChildDirect(new Fqn(childName));
created = true;
if (trace) log.trace("Child node was null, so created child node
" + childName);
+ if (createdNodes != null) createdNodes.add(currentNode.getFqn());
+ if (markNewNodesAsDeleted) currentNode.markAsDeleted(true);
}
else
{
@@ -478,7 +493,7 @@
}
/**
- * Used by {@link #lock(org.jboss.cache.InvocationContext, org.jboss.cache.Fqn,
org.jboss.cache.lock.NodeLock.LockType, boolean, long, boolean, boolean)}.
+ * Used by lock()
* Determins whter an arbitrary node from the supplied fqn needs an write lock.
*/
private boolean writeLockNeeded(InvocationContext ctx, NodeLock.LockType lockType, int
currentNodeIndex, boolean acquireWriteLockOnParent, boolean createIfNotExists, Fqn
targetFqn, NodeSPI currentNode)
Modified:
core/trunk/src/main/java/org/jboss/cache/invocation/RemoteCacheInvocationDelegate.java
===================================================================
---
core/trunk/src/main/java/org/jboss/cache/invocation/RemoteCacheInvocationDelegate.java 2008-01-04
18:52:20 UTC (rev 4992)
+++
core/trunk/src/main/java/org/jboss/cache/invocation/RemoteCacheInvocationDelegate.java 2008-01-04
19:03:15 UTC (rev 4993)
@@ -73,12 +73,14 @@
{
if (log.isTraceEnabled())
log.trace("DataGravitationCleanup: Removing primary (" + primary +
") and backup (" + backup + ")");
- //primaryDataCleanup =
MethodCallFactory.create(MethodDeclarations.removeNodeMethodLocal, primary, false);
+
getInvocationContext().getOptionOverrides().setCacheModeLocal(true);
- removeNode(primary);
- //backupDataCleanup =
MethodCallFactory.create(MethodDeclarations.removeNodeMethodLocal, backup, false);
- getInvocationContext().getOptionOverrides().setCacheModeLocal(true);
- removeNode(backup);
+ if (!removeNode(primary))
+ {
+ // only attempt to clean up the backup if the primary did not exist - a waste
of a call otherwise.
+ getInvocationContext().getOptionOverrides().setCacheModeLocal(true);
+ removeNode(backup);
+ }
}
else
{
Modified: core/trunk/src/test/java/org/jboss/cache/api/CacheAPIOptimisticTest.java
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/api/CacheAPIOptimisticTest.java 2008-01-04
18:52:20 UTC (rev 4992)
+++ core/trunk/src/test/java/org/jboss/cache/api/CacheAPIOptimisticTest.java 2008-01-04
19:03:15 UTC (rev 4993)
@@ -1,9 +1,12 @@
package org.jboss.cache.api;
+import org.testng.annotations.Test;
+
/**
* Optimistically locked version of {@link org.jboss.cache.api.CacheAPITest}
*/
+@Test(groups = "functional")
public class CacheAPIOptimisticTest extends CacheAPITest
{
public CacheAPIOptimisticTest()
Modified: core/trunk/src/test/java/org/jboss/cache/api/CacheAPITest.java
===================================================================
--- core/trunk/src/test/java/org/jboss/cache/api/CacheAPITest.java 2008-01-04 18:52:20 UTC
(rev 4992)
+++ core/trunk/src/test/java/org/jboss/cache/api/CacheAPITest.java 2008-01-04 19:03:15 UTC
(rev 4993)
@@ -2,6 +2,7 @@
import org.jboss.cache.Cache;
import org.jboss.cache.CacheFactory;
+import org.jboss.cache.CacheSPI;
import org.jboss.cache.DefaultCacheFactory;
import org.jboss.cache.Fqn;
import org.jboss.cache.Node;
@@ -12,11 +13,13 @@
import org.jboss.cache.notifications.annotation.NodeCreated;
import org.jboss.cache.notifications.event.Event;
import org.jboss.cache.transaction.GenericTransactionManagerLookup;
+import org.jboss.cache.util.CachePrinter;
import static org.testng.AssertJUnit.*;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
+import javax.transaction.TransactionManager;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -28,7 +31,7 @@
* @author <a href="mailto:manik@jboss.org">Manik Surtani</a>
*/
-@Test(groups = {"functional"})
+@Test(groups = "functional")
public class CacheAPITest
{
private Cache<String, String> cache;
@@ -185,6 +188,8 @@
assertFalse(cache.getRoot().hasChild(fqn));
assertEquals(false, cache.removeNode(fqn));
+ System.out.println("Cache: " + CachePrinter.printCacheDetails(cache));
+
// Check that it's removed if it has a child
Fqn<String> child = Fqn.fromString("/test/fqn/child");
cache.getRoot().addChild(child);
@@ -350,6 +355,29 @@
assertTrue(cache.getRoot().getChildren().isEmpty());
}
+ public void testPhantomStructuralNodesOnRemove()
+ {
+ CacheSPI spi = (CacheSPI) cache;
+ assert spi.peek(Fqn.fromString("/a/b/c"), true, true) == null;
+ assert !spi.removeNode("/a/b/c");
+ assert spi.peek(Fqn.fromString("/a/b/c"), true, true) == null;
+ assert spi.peek(Fqn.fromString("/a/b"), true, true) == null;
+ assert spi.peek(Fqn.fromString("/a"), true, true) == null;
+ }
+
+ public void testPhantomStructuralNodesOnRemoveTransactional() throws Exception
+ {
+ CacheSPI spi = (CacheSPI) cache;
+ TransactionManager tm = spi.getTransactionManager();
+ assert spi.peek(Fqn.fromString("/a/b/c"), true, true) == null;
+ tm.begin();
+ assert !spi.removeNode("/a/b/c");
+ tm.commit();
+ assert spi.peek(Fqn.fromString("/a/b/c"), true, true) == null;
+ assert spi.peek(Fqn.fromString("/a/b"), true, true) == null;
+ assert spi.peek(Fqn.fromString("/a"), true, true) == null;
+ }
+
@CacheListener
public class Listener
{
Modified:
core/trunk/src/test/java/org/jboss/cache/buddyreplication/GravitationCleanupTest.java
===================================================================
---
core/trunk/src/test/java/org/jboss/cache/buddyreplication/GravitationCleanupTest.java 2008-01-04
18:52:20 UTC (rev 4992)
+++
core/trunk/src/test/java/org/jboss/cache/buddyreplication/GravitationCleanupTest.java 2008-01-04
19:03:15 UTC (rev 4993)
@@ -15,10 +15,30 @@
Fqn fqn = Fqn.fromString("/a/b/c");
Object key = "key", value = "value";
- public void testStaleRegionOnDataOwner() throws Exception
+ public void testStaleRegionOnDataOwnerPessimistic() throws Exception
{
- caches = createCaches(1, 2, false, true, false);
+ testDataOwner(false);
+ }
+ public void testStaleRegionOnDataOwnerOptimistic() throws Exception
+ {
+ testDataOwner(true);
+ }
+
+ public void testStaleRegionOnBuddyPessimistic() throws Exception
+ {
+ testBuddy(false);
+ }
+
+ public void testStaleRegionOnBuddyOptimistic() throws Exception
+ {
+ testBuddy(true);
+ }
+
+ private void testDataOwner(boolean optimistic) throws Exception
+ {
+ caches = createCaches(1, 2, false, true, optimistic);
+
// add some stuff on the primary
CacheSPI dataOwner = caches.get(0);
CacheSPI buddy = caches.get(1);
@@ -52,4 +72,54 @@
assert dataOwner.peek(new Fqn(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN,
BuddyManager.getGroupNameFromAddress(buddy.getLocalAddress())), false) != null :
"Should have backup node for buddy";
assert dataOwner.peek(BuddyManager.getBackupFqn(buddy.getLocalAddress(), fqn),
false) != null : "Should have backup data";
}
+
+ private void testBuddy(boolean optimistic) throws Exception
+ {
+ caches = createCaches(1, 3, false, true, optimistic);
+
+ // add some stuff on the primary
+ CacheSPI dataOwner = caches.get(0);
+ CacheSPI buddy = caches.get(1);
+ CacheSPI thirdInstance = caches.get(2);
+
+ assertIsBuddy(dataOwner, buddy, true);
+ assertIsBuddy(buddy, thirdInstance, true);
+ assertIsBuddy(thirdInstance, dataOwner, true);
+
+ dataOwner.put(fqn, key, value);
+
+ System.out.println("dataOwner: " +
CachePrinter.printCacheLockingInfo(dataOwner));
+ System.out.println("buddy: " +
CachePrinter.printCacheLockingInfo(buddy));
+ System.out.println("thirdInstance: " +
CachePrinter.printCacheLockingInfo(thirdInstance));
+
+ assert dataOwner.peek(fqn, false) != null : "Should have data";
+ assert dataOwner.peek(new Fqn(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN,
BuddyManager.getGroupNameFromAddress(thirdInstance.getLocalAddress())), false) != null :
"Should have backup node for buddy";
+ assert dataOwner.peek(new Fqn(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN,
BuddyManager.getGroupNameFromAddress(dataOwner.getLocalAddress())), false) == null :
"Should NOT have backup node for self!";
+ assert dataOwner.peek(new Fqn(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN,
BuddyManager.getGroupNameFromAddress(buddy.getLocalAddress())), false) == null :
"Should NOT have backup node for 2nd instance!";
+
+ assert buddy.peek(fqn, false) == null : "Should not have data";
+ assert buddy.peek(new Fqn(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN,
BuddyManager.getGroupNameFromAddress(buddy.getLocalAddress())), false) == null :
"Should NOT have backup node for self!";
+ assert buddy.peek(new Fqn(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN,
BuddyManager.getGroupNameFromAddress(dataOwner.getLocalAddress())), false) != null :
"Should have backup node for buddy";
+ assert buddy.peek(BuddyManager.getBackupFqn(dataOwner.getLocalAddress(), fqn),
false) != null : "Should have backup data";
+
+ // now do a gravitate call.
+ assert thirdInstance.get(fqn, key).equals(value) : "Data should have
gravitated!";
+
+ System.out.println("dataOwner: " +
CachePrinter.printCacheLockingInfo(dataOwner));
+ System.out.println("buddy: " +
CachePrinter.printCacheLockingInfo(buddy));
+ System.out.println("thirdInstance: " +
CachePrinter.printCacheLockingInfo(thirdInstance));
+
+ assert thirdInstance.peek(fqn, false) != null : "Should have data";
+ assert thirdInstance.peek(new Fqn(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN,
BuddyManager.getGroupNameFromAddress(buddy.getLocalAddress())), false) != null :
"Should have backup node for buddy";
+ assert thirdInstance.peek(new Fqn(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN,
BuddyManager.getGroupNameFromAddress(thirdInstance.getLocalAddress())), false) == null :
"Should NOT have backup node for self!";
+
+ assert dataOwner.peek(fqn, false) == null : "Should not have data";
+ assert dataOwner.peek(new Fqn(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN,
BuddyManager.getGroupNameFromAddress(dataOwner.getLocalAddress())), false) == null :
"Should NOT have backup node for self!";
+ assert dataOwner.peek(new Fqn(BuddyManager.BUDDY_BACKUP_SUBTREE_FQN,
BuddyManager.getGroupNameFromAddress(thirdInstance.getLocalAddress())), false) != null :
"Should have backup node for buddy";
+ assert dataOwner.peek(BuddyManager.getBackupFqn(thirdInstance.getLocalAddress(),
fqn), false) != null : "Should have backup data";
+ assert buddy.peek(fqn, false) == null : "Should not have data";
+ assert buddy.peek(fqn.getParent(), false) == null : "Should not have any part
of the data";
+ assert buddy.peek(BuddyManager.getBackupFqn(dataOwner.getLocalAddress(), fqn),
false) == null : "Should NOT have backup data";
+
+ }
}