Skip to content

Commit

Permalink
Fix problem with terminating inner transactions
Browse files Browse the repository at this point in the history
PlaceboTransaction was not getting a KernelTransaction, it was getting a
Supplier<KernelTransaction>, which would return the currently thread bound transaction.

This works well in most cases, since transactions are thread bound and should not be used
from other threads. When using terminate() though, the caller is almost always from a different thread,
and in these situations we can't use a supplier.
  • Loading branch information
systay committed Oct 4, 2016
1 parent debfe02 commit 35b55a5
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 8 deletions.
Expand Up @@ -32,25 +32,25 @@ public class PlaceboTransaction implements InternalTransaction
{
private final static PropertyContainerLocker locker = new PropertyContainerLocker();
private final Supplier<Statement> stmt;
private final Supplier<KernelTransaction> currentTransaction;
private final KernelTransaction currentTransaction;
private boolean success;

public PlaceboTransaction( Supplier<KernelTransaction> currentTransaction, Supplier<Statement> stmt )
{
this.stmt = stmt;
this.currentTransaction = currentTransaction;
this.currentTransaction = currentTransaction.get();
}

@Override
public void terminate()
{
currentTransaction.get().markForTermination( Status.Transaction.Terminated );
currentTransaction.markForTermination( Status.Transaction.Terminated );
}

@Override
public void failure()
{
currentTransaction.get().failure();
currentTransaction.failure();
}

@Override
Expand All @@ -64,7 +64,7 @@ public void close()
{
if ( !success )
{
currentTransaction.get().failure();
currentTransaction.failure();
}
}

Expand All @@ -83,18 +83,18 @@ public Lock acquireReadLock( PropertyContainer entity )
@Override
public KernelTransaction.Type transactionType()
{
return currentTransaction.get().transactionType();
return currentTransaction.transactionType();
}

@Override
public AccessMode mode()
{
return currentTransaction.get().mode();
return currentTransaction.mode();
}

@Override
public KernelTransaction.Revertable restrict( AccessMode mode )
{
return currentTransaction.get().restrict( mode );
return currentTransaction.restrict( mode );
}
}
Expand Up @@ -174,6 +174,94 @@ public void terminateNestedTransactionThrowsExceptionOnNextNestedOperation() thr
db.shutdown();
}

@Test
public void terminateNestedTransactionThrowsExceptionOnNextNestedOperationMultiThreadedVersion() throws Exception
{
// Given
final GraphDatabaseService db = new TestGraphDatabaseFactory().newImpermanentDatabase();
try
{
// When
final CountDownLatch txSet = new CountDownLatch( 1 );
final CountDownLatch terminated = new CountDownLatch( 1 );
final Transaction[] outer = {null};
final Exception[] threadFail = {null};

Thread worker = new Thread( () ->
{
try ( Transaction inner = db.beginTx() )
{
outer[0] = inner;
txSet.countDown();
terminated.await();
db.createNode();
fail( "should have failed earlier" );
}
catch ( Exception e )
{
threadFail[0] = e;
}
} );
worker.start();
txSet.await();
outer[0].terminate();
terminated.countDown();
worker.join();
assertThat(threadFail[0], instanceOf(TransactionTerminatedException.class));
}
finally
{
db.shutdown();
}
}

@Test
public void terminateNestedTransactionThrowsExceptionOnNextNestedOperationMultiThreadedVersionWithNestedTx()
throws Exception
{
// Given
final GraphDatabaseService db = new TestGraphDatabaseFactory().newImpermanentDatabase();
try
{
// When
final CountDownLatch txSet = new CountDownLatch( 1 );
final CountDownLatch terminated = new CountDownLatch( 1 );
final Transaction[] outer = {null};
final Exception[] threadFail = {null};

Thread worker = new Thread( () ->
{
Transaction transaction = db.beginTx();
try ( Transaction inner = db.beginTx() )
{
outer[0] = inner;
txSet.countDown();
terminated.await();
db.createNode();
fail( "should have failed earlier" );
}
catch ( Exception e )
{
threadFail[0] = e;
}
finally
{
transaction.close();
}
} );
worker.start();
txSet.await();
outer[0].terminate();
terminated.countDown();
worker.join();
assertThat(threadFail[0], instanceOf(TransactionTerminatedException.class));
}
finally
{
db.shutdown();
}
}

@Test
public void givenDatabaseAndStartedTxWhenShutdownAndStartNewTxThenBeginTxTimesOut() throws Exception
{
Expand Down

0 comments on commit 35b55a5

Please sign in to comment.