diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/locking/TerminationCompatibility.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/locking/TerminationCompatibility.java index 5ad10e5485f30..e30b1e82f648c 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/locking/TerminationCompatibility.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/locking/TerminationCompatibility.java @@ -210,6 +210,27 @@ public void closeClientAfterExclusiveLockFailureOnTransactionTermination() throw closeClientAfterLockFailureOnTransactionTermination( false ); } + @Test + public void acquireExclusiveLockWhileHoldingSharedLockCanBeTerminated() throws Exception + { + acquireSharedLockInThisThread(); + + CountDownLatch sharedLockAcquired = new CountDownLatch( 1 ); + CountDownLatch startExclusiveLock = new CountDownLatch( 1 ); + LockAcquisition acquisition = acquireSharedAndExclusiveLocksInAnotherThread( sharedLockAcquired, + startExclusiveLock ); + + await( sharedLockAcquired ); + startExclusiveLock.countDown(); + assertThreadIsWaitingForLock( acquisition ); + + acquisition.terminate(); + assertLockAcquisitionFailed( acquisition ); + + releaseAllLocksInThisThread(); + assertNoLocksHeld(); + } + private void closeClientAfterLockFailureOnTransactionTermination( boolean shared ) throws Exception { acquireExclusiveLockInThisThread(); @@ -249,6 +270,12 @@ private void acquireLockAfterOtherLockFailureOnTransactionTerminationSameThread( assertLockAcquisitionSucceeded( lockAcquisition ); } + private void acquireSharedLockInThisThread() + { + client.acquireShared( RESOURCE_TYPE, RESOURCE_ID ); + assertLocksHeld( RESOURCE_ID ); + } + private void acquireExclusiveLockInThisThread() { client.acquireExclusive( RESOURCE_TYPE, RESOURCE_ID ); @@ -349,6 +376,33 @@ public Void call() throws Exception return lockAcquisition; } + private LockAcquisition acquireSharedAndExclusiveLocksInAnotherThread( final CountDownLatch sharedLockAcquired, + final CountDownLatch startExclusiveLock ) + { + final LockAcquisition lockAcquisition = new LockAcquisition(); + + Future future = executor.submit( new Callable() + { + @Override + public Void call() throws Exception + { + try ( Client client = newLockClient( lockAcquisition ) ) + { + client.acquireShared( RESOURCE_TYPE, RESOURCE_ID ); + + sharedLockAcquired.countDown(); + await( startExclusiveLock ); + + client.acquireExclusive( RESOURCE_TYPE, RESOURCE_ID ); + } + return null; + } + } ); + lockAcquisition.setFuture( future ); + + return lockAcquisition; + } + private LockAcquisition tryAcquireTwoLocksLockInAnotherThread( final boolean shared, final CountDownLatch firstLockAcquired ) { diff --git a/enterprise/ha/src/main/java/org/neo4j/kernel/ha/lock/forseti/ForsetiClient.java b/enterprise/ha/src/main/java/org/neo4j/kernel/ha/lock/forseti/ForsetiClient.java index 2840df034d28b..04101a2f270a8 100644 --- a/enterprise/ha/src/main/java/org/neo4j/kernel/ha/lock/forseti/ForsetiClient.java +++ b/enterprise/ha/src/main/java/org/neo4j/kernel/ha/lock/forseti/ForsetiClient.java @@ -615,21 +615,33 @@ private boolean tryUpgradeToExclusiveWithShareLockHeld( return true; } - catch(DeadlockDetectedException e) + catch ( DeadlockDetectedException e ) { - sharedLock.releaseUpdateLock(this); + sharedLock.releaseUpdateLock( this ); + // wait list is not cleared here as in other catch blocks because it is cleared in + // markAsWaitingFor() before throwing DeadlockDetectedException throw e; } - catch(Throwable e) + catch ( TransactionTerminatedException e ) + { + handleUpgradeToExclusiveFailure( sharedLock ); + throw e; + } + catch ( Throwable e ) { - sharedLock.releaseUpdateLock(this); - clearWaitList(); + handleUpgradeToExclusiveFailure( sharedLock ); throw new RuntimeException( e ); } } return false; } + private void handleUpgradeToExclusiveFailure( SharedLock sharedLock ) + { + sharedLock.releaseUpdateLock( this ); + clearWaitList(); + } + private void clearWaitList() { waitList.clear();