Search in sources :

Example 6 with BinaryLatch

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);
}
Also used : GraphDatabaseService(org.neo4j.graphdb.GraphDatabaseService) ForsetiClient(org.neo4j.kernel.impl.locking.forseti.ForsetiClient) InternalTransactionEventListener(org.neo4j.kernel.internal.event.InternalTransactionEventListener) BinaryLatch(org.neo4j.util.concurrent.BinaryLatch) Transaction(org.neo4j.graphdb.Transaction) OtherThreadExecutor(org.neo4j.test.OtherThreadExecutor) MutableLong(org.apache.commons.lang3.mutable.MutableLong) AtomicLong(java.util.concurrent.atomic.AtomicLong) TransactionData(org.neo4j.graphdb.event.TransactionData) Test(org.junit.jupiter.api.Test)

Example 7 with BinaryLatch

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());
}
Also used : AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) InternalTransaction(org.neo4j.kernel.impl.coreapi.InternalTransaction) Transaction(org.neo4j.graphdb.Transaction) KernelTransaction(org.neo4j.kernel.api.KernelTransaction) Node(org.neo4j.graphdb.Node) BinaryLatch(org.neo4j.util.concurrent.BinaryLatch) Test(org.junit.jupiter.api.Test) Timeout(org.junit.jupiter.api.Timeout)

Example 8 with BinaryLatch

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();
    }
}
Also used : Transaction(org.neo4j.graphdb.Transaction) KernelTransaction(org.neo4j.kernel.api.KernelTransaction) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) Node(org.neo4j.graphdb.Node) ExecutorService(java.util.concurrent.ExecutorService) RelationshipType(org.neo4j.graphdb.RelationshipType) BinaryLatch(org.neo4j.util.concurrent.BinaryLatch) Test(org.junit.jupiter.api.Test)

Example 9 with BinaryLatch

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();
        }
    }
}
Also used : ExecutorService(java.util.concurrent.ExecutorService) AtomicReference(java.util.concurrent.atomic.AtomicReference) BinaryLatch(org.neo4j.util.concurrent.BinaryLatch) ValueSource(org.junit.jupiter.params.provider.ValueSource) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest)

Example 10 with BinaryLatch

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();
}
Also used : BinaryLatch(org.neo4j.util.concurrent.BinaryLatch) ValueSource(org.junit.jupiter.params.provider.ValueSource) ParameterizedTest(org.junit.jupiter.params.ParameterizedTest)

Aggregations

BinaryLatch (org.neo4j.util.concurrent.BinaryLatch)31 Test (org.junit.jupiter.api.Test)21 RepeatedTest (org.junit.jupiter.api.RepeatedTest)5 Transaction (org.neo4j.graphdb.Transaction)5 ExecutorService (java.util.concurrent.ExecutorService)4 ParameterizedTest (org.junit.jupiter.params.ParameterizedTest)4 Node (org.neo4j.graphdb.Node)4 KernelTransaction (org.neo4j.kernel.api.KernelTransaction)4 AtomicLong (java.util.concurrent.atomic.AtomicLong)3 Timeout (org.junit.jupiter.api.Timeout)3 ValueSource (org.junit.jupiter.params.provider.ValueSource)3 InternalTransaction (org.neo4j.kernel.impl.coreapi.InternalTransaction)3 Flushable (java.io.Flushable)2 Path (java.nio.file.Path)2 ArrayList (java.util.ArrayList)2 Future (java.util.concurrent.Future)2 AtomicBoolean (java.util.concurrent.atomic.AtomicBoolean)2 AtomicInteger (java.util.concurrent.atomic.AtomicInteger)2 AtomicReference (java.util.concurrent.atomic.AtomicReference)2 RelationshipType (org.neo4j.graphdb.RelationshipType)2