use of org.neo4j.util.concurrent.BinaryLatch in project neo4j by neo4j.
the class DatabaseUpgradeTransactionIT method shouldHandleDeadlocksOnUpgradeTransaction.
@Test
void shouldHandleDeadlocksOnUpgradeTransaction() throws Exception {
// This test tries to simulate a rare but possible deadlock scenario where one ongoing transaction (in commit phase) is waiting for a lock held by the
// transaction doing the upgrade. Since the first transaction has a shared upgrade lock, and the upgrade transaction wants the write lock,
// this will deadlock. Depending on which "side" the deadlock happens on, one of two things can happen
// 1. the conflicting transaction will fail with DeadlockDetectedException and the upgrade will complete successfully or
// 2. the upgrade fails, were we let both conflicting and trigger transaction complete normally, log the failure and try upgrade later.
// This tests the latter, as the first is not interesting from an upgrade perspective
// Given
setKernelVersion(V4_2);
setDbmsRuntime(DbmsRuntimeVersion.V4_2);
restartDbms();
long lockNode1 = createWriteTransaction();
long lockNode2 = createWriteTransaction();
BinaryLatch l1 = new BinaryLatch();
BinaryLatch l2 = new BinaryLatch();
long numNodesBefore = getNodeCount();
// Since the upgrade handler is already installed, we know this will be invoked after that, having the shared upgrade lock
dbms.registerTransactionEventListener(db.databaseName(), new InternalTransactionEventListener.Adapter<>() {
@Override
public Object beforeCommit(TransactionData data, Transaction transaction, GraphDatabaseService databaseService) {
// Unregister so only the first transaction gets this
dbms.unregisterTransactionEventListener(db.databaseName(), this);
l2.release();
// Hold here until the upgrade transaction is ongoing
l1.await();
// we need to lock several entities here since deadlock termination is based on number of locks client holds.
// and we want other transaction to be canceled
transaction.acquireWriteLock(transaction.getNodeById(lockNode2));
// Then wait for the lock held by that "triggering" tx
transaction.acquireWriteLock(transaction.getNodeById(lockNode1));
return null;
}
});
// When
try (OtherThreadExecutor executor = new OtherThreadExecutor("Executor")) {
// This will trigger the "locking" listener but not the upgrade
Future<Long> f1 = executor.executeDontWait(this::createWriteTransaction);
// wait for it to be committing
l2.await();
// then upgrade dbms runtime to trigger db upgrade on next write
setDbmsRuntime(DbmsRuntimeVersion.LATEST_DBMS_RUNTIME_COMPONENT_VERSION);
try (Transaction tx = db.beginTx()) {
// take the lock
tx.acquireWriteLock(tx.getNodeById(lockNode1));
// and make sure it is a write to trigger upgrade
tx.createNode();
l1.release();
executor.waitUntilWaiting(details -> details.isAt(ForsetiClient.class, "acquireExclusive"));
tx.commit();
}
executor.awaitFuture(f1);
}
// Then
LogAssertions.assertThat(logProvider).containsMessageWithArguments("Upgrade transaction from %s to %s not possible right now due to conflicting transaction, will retry on next write", V4_2, LATEST).doesNotContainMessageWithArguments("Upgrade transaction from %s to %s started", V4_2, LATEST);
assertThat(getNodeCount()).as("Both transactions succeeded").isEqualTo(numNodesBefore + 2);
assertThat(getKernelVersion()).isEqualTo(V4_2);
// When
createWriteTransaction();
// Then
assertThat(getKernelVersion()).isEqualTo(LATEST);
LogAssertions.assertThat(logProvider).containsMessageWithArguments("Upgrade transaction from %s to %s started", V4_2, LATEST).containsMessageWithArguments("Upgrade transaction from %s to %s completed", V4_2, LATEST);
}
use of org.neo4j.util.concurrent.BinaryLatch in project neo4j by neo4j.
the class KernelTransactionTimeoutMonitorIT method terminatingTransactionMustEagerlyReleaseTheirLocks.
@Test
@Timeout(30)
void terminatingTransactionMustEagerlyReleaseTheirLocks() throws Exception {
AtomicBoolean nodeLockAcquired = new AtomicBoolean();
AtomicBoolean lockerDone = new AtomicBoolean();
BinaryLatch lockerPause = new BinaryLatch();
long nodeId;
try (Transaction tx = database.beginTx()) {
nodeId = tx.createNode().getId();
tx.commit();
}
Future<?> locker = executor.submit(() -> {
try (Transaction tx = database.beginTx()) {
Node node = tx.getNodeById(nodeId);
tx.acquireReadLock(node);
nodeLockAcquired.set(true);
lockerPause.await();
}
lockerDone.set(true);
});
boolean proceed;
do {
proceed = nodeLockAcquired.get();
} while (!proceed);
terminateOngoingTransaction();
// but the thread should still be blocked on the latch
assertFalse(lockerDone.get());
// Yet we should be able to proceed and grab the locks they once held
try (Transaction tx = database.beginTx()) {
// Write-locking is only possible if their shared lock was released
tx.acquireWriteLock(tx.getNodeById(nodeId));
tx.commit();
}
// No exception from our lock client being stopped (e.g. we ended up blocked for too long) or from timeout
lockerPause.release();
locker.get();
assertTrue(lockerDone.get());
}
use of org.neo4j.util.concurrent.BinaryLatch in project neo4j by neo4j.
the class TransactionEventsIT method registerUnregisterWithConcurrentTransactions.
@Test
void registerUnregisterWithConcurrentTransactions() throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
try {
AtomicInteger runningCounter = new AtomicInteger();
AtomicInteger doneCounter = new AtomicInteger();
BinaryLatch startLatch = new BinaryLatch();
RelationshipType relationshipType = RelationshipType.withName("REL");
CountingTransactionEventListener[] handlers = new CountingTransactionEventListener[20];
for (int i = 0; i < handlers.length; i++) {
handlers[i] = new CountingTransactionEventListener();
}
long relNodeId;
try (Transaction tx = db.beginTx()) {
relNodeId = tx.createNode().getId();
tx.commit();
}
Future<?> nodeCreator = executor.submit(() -> {
try {
runningCounter.incrementAndGet();
startLatch.await();
for (int i = 0; i < 2_000; i++) {
try (Transaction tx = db.beginTx()) {
tx.createNode();
if (ThreadLocalRandom.current().nextBoolean()) {
tx.commit();
}
}
}
} finally {
doneCounter.incrementAndGet();
}
});
Future<?> relationshipCreator = executor.submit(() -> {
try {
runningCounter.incrementAndGet();
startLatch.await();
for (int i = 0; i < 1_000; i++) {
try (Transaction tx = db.beginTx()) {
Node relNode = tx.getNodeById(relNodeId);
relNode.createRelationshipTo(relNode, relationshipType);
if (ThreadLocalRandom.current().nextBoolean()) {
tx.commit();
}
}
}
} finally {
doneCounter.incrementAndGet();
}
});
while (runningCounter.get() < 2) {
Thread.yield();
}
int i = 0;
dbms.registerTransactionEventListener(DEFAULT_DATABASE_NAME, handlers[i]);
CountingTransactionEventListener currentlyRegistered = handlers[i];
i++;
startLatch.release();
while (doneCounter.get() < 2) {
dbms.registerTransactionEventListener(DEFAULT_DATABASE_NAME, handlers[i]);
i++;
if (i == handlers.length) {
i = 0;
}
dbms.unregisterTransactionEventListener(DEFAULT_DATABASE_NAME, currentlyRegistered);
currentlyRegistered = handlers[i];
}
nodeCreator.get();
relationshipCreator.get();
for (CountingTransactionEventListener handler : handlers) {
assertEquals(0, handler.get());
}
} finally {
executor.shutdown();
}
}
use of org.neo4j.util.concurrent.BinaryLatch in project neo4j by neo4j.
the class LatchMapTest method takeOrAwaitLatchMustAwaitExistingLatchAndReturnNull.
@ValueSource(ints = { LatchMap.DEFAULT_FAULT_LOCK_STRIPING, 1 << 10, 1 << 11 })
@ParameterizedTest
void takeOrAwaitLatchMustAwaitExistingLatchAndReturnNull(int size) throws Exception {
LatchMap latches = new LatchMap(size);
AtomicReference<Thread> threadRef = new AtomicReference<>();
BinaryLatch latch = latches.takeOrAwaitLatch(42);
assertThat(latch).isNotNull();
ExecutorService executor = null;
try {
executor = Executors.newSingleThreadExecutor();
Future<BinaryLatch> future = executor.submit(() -> {
threadRef.set(Thread.currentThread());
return latches.takeOrAwaitLatch(42);
});
Thread th;
do {
th = threadRef.get();
} while (th == null);
ThreadTestUtils.awaitThreadState(th, 10_000, Thread.State.WAITING);
latch.release();
assertThat(future.get(1, TimeUnit.SECONDS)).isNull();
} finally {
if (executor != null) {
executor.shutdown();
}
}
}
use of org.neo4j.util.concurrent.BinaryLatch in project neo4j by neo4j.
the class LatchMapTest method takeOrAwaitLatchMustReturnLatchIfAvailable.
@ValueSource(ints = { LatchMap.DEFAULT_FAULT_LOCK_STRIPING, 1 << 10, 1 << 11 })
@ParameterizedTest
void takeOrAwaitLatchMustReturnLatchIfAvailable(int size) {
LatchMap latches = new LatchMap(size);
BinaryLatch latch = latches.takeOrAwaitLatch(0);
assertThat(latch).isNotNull();
latch.release();
}
Aggregations