StampedLongAdderLockManagerTest.java

/*******************************************************************************
 * Copyright (c) 2025 Eclipse RDF4J contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *******************************************************************************/
package org.eclipse.rdf4j.common.concurrent.locks;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.junit.jupiter.api.Test;

class StampedLongAdderLockManagerTest {

	@Test
	void writeLockWaitsForReaders() throws Exception {
		StampedLongAdderLockManager manager = new StampedLongAdderLockManager();
		long readStamp = manager.readLock();
		assertTrue(manager.isReaderActive());

		ExecutorService executor = Executors.newSingleThreadExecutor();
		try {
			CountDownLatch attemptingWrite = new CountDownLatch(1);
			AtomicBoolean acquiredWrite = new AtomicBoolean(false);

			Future<Long> writeFuture = executor.submit(() -> {
				attemptingWrite.countDown();
				long stamp = manager.writeLock();
				acquiredWrite.set(true);
				return stamp;
			});

			assertTrue(attemptingWrite.await(500, TimeUnit.MILLISECONDS), "write attempt did not start in time");
			TimeUnit.MILLISECONDS.sleep(100);
			assertFalse(acquiredWrite.get(), "write lock acquired while read lock active");

			manager.unlockRead(readStamp);
			long writeStamp = writeFuture.get(2, TimeUnit.SECONDS);
			assertTrue(acquiredWrite.get());
			assertTrue(manager.isWriterActive());
			manager.unlockWrite(writeStamp);
			assertFalse(manager.isWriterActive());
		} finally {
			executor.shutdownNow();
			executor.awaitTermination(1, TimeUnit.SECONDS);
		}
	}

	@Test
	void readLockWaitsForWriters() throws Exception {
		StampedLongAdderLockManager manager = new StampedLongAdderLockManager();
		long writeStamp = manager.writeLock();
		assertTrue(manager.isWriterActive());

		ExecutorService executor = Executors.newSingleThreadExecutor();
		try {
			CountDownLatch attemptingRead = new CountDownLatch(1);
			AtomicBoolean acquiredRead = new AtomicBoolean(false);

			Future<Long> readFuture = executor.submit(() -> {
				attemptingRead.countDown();
				long stamp = manager.readLock();
				acquiredRead.set(true);
				return stamp;
			});

			assertTrue(attemptingRead.await(500, TimeUnit.MILLISECONDS), "read attempt did not start in time");
			TimeUnit.MILLISECONDS.sleep(100);
			assertFalse(acquiredRead.get(), "read lock acquired while write lock active");

			manager.unlockWrite(writeStamp);
			long readStamp = readFuture.get(2, TimeUnit.SECONDS);
			assertTrue(acquiredRead.get());
			assertEquals(StampedLongAdderLockManager.READ_LOCK_STAMP, readStamp);
			assertTrue(manager.isReaderActive());
			manager.unlockRead(readStamp);
			assertFalse(manager.isReaderActive());
		} finally {
			executor.shutdownNow();
			executor.awaitTermination(1, TimeUnit.SECONDS);
		}
	}
}