All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.rdf4j.common.concurrent.locks.AbstractReadWriteLockManager Maven / Gradle / Ivy

There is a newer version: 5.1.0
Show newest version
/*******************************************************************************
 * Copyright (c) 2022 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 java.lang.invoke.VarHandle;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
import java.util.concurrent.locks.StampedLock;

import org.eclipse.rdf4j.common.concurrent.locks.diagnostics.LockCleaner;
import org.eclipse.rdf4j.common.concurrent.locks.diagnostics.LockDiagnostics;
import org.eclipse.rdf4j.common.concurrent.locks.diagnostics.LockMonitoring;
import org.eclipse.rdf4j.common.concurrent.locks.diagnostics.LockTracking;
import org.slf4j.LoggerFactory;

/**
 * An abstract base implementation of a read/write-lock manager.
 *
 * @author Håvard M. Ottestad
 */
public abstract class AbstractReadWriteLockManager implements ReadWriteLockManager {

	private final LockMonitoring readLockMonitoring;
	private final LockMonitoring writeLockMonitoring;

	// StampedLock for handling writers.
	final StampedLock stampedLock = new StampedLock();

	// LongAdder for handling readers. When the count is equal then there are no active readers.
	final LongAdder readersLocked = new LongAdder();
	final LongAdder readersUnlocked = new LongAdder();

	// milliseconds to wait when calling the try-lock method of the stamped lock
	private final int tryWriteLockMillis;

	/**
	 * When acquiring a write-lock, the thread will acquire the write-lock and then spin & yield while waiting for
	 * readers to unlock their locks. A deadlock is possible if someone already holding a read-lock acquires another
	 * read-lock at the same time that another thread is waiting for a write-lock. To stop this from happening we can
	 * set READ_PREFERENCE to a number higher than zero. READ_PREFERENCE of 1 means that the thread acquiring a
	 * write-lock will release the write-lock if there are any readers. A READ_PREFERENCE of 100 means that the thread
	 * acquiring a write-lock will spin & yield 100 times before it attempts to release the write-lock.
	 */
	final int writePreference;

	public AbstractReadWriteLockManager() {
		this(false);
	}

	public AbstractReadWriteLockManager(boolean trackLocks) {
		this(trackLocks, LockMonitoring.INITIAL_WAIT_TO_COLLECT);
	}

	public AbstractReadWriteLockManager(boolean trackLocks, int waitToCollect) {
		this("", waitToCollect, LockDiagnostics.fromLegacyTracking(trackLocks));
	}

	public AbstractReadWriteLockManager(String alias, LockDiagnostics... lockDiagnostics) {
		this(alias, LockMonitoring.INITIAL_WAIT_TO_COLLECT, lockDiagnostics);
	}

	public AbstractReadWriteLockManager(String alias, int waitToCollect, LockDiagnostics... lockDiagnostics) {

		this.tryWriteLockMillis = Math.min(1000, waitToCollect);

		// WRITE_PREFERENCE can not be negative or 0.
		this.writePreference = Math.max(1, getWriterPreference());

		boolean releaseAbandoned = false;
		boolean detectStalledOrDeadlock = false;
		boolean stackTrace = false;

		for (LockDiagnostics lockDiagnostic : lockDiagnostics) {
			switch (lockDiagnostic) {
			case releaseAbandoned:
				releaseAbandoned = true;
				break;
			case detectStalledOrDeadlock:
				detectStalledOrDeadlock = true;
				break;
			case stackTrace:
				stackTrace = true;
				break;
			}
		}

		if (lockDiagnostics.length == 0) {
			readLockMonitoring = LockMonitoring
					.wrap(Lock.ExtendedSupplier.wrap(this::createReadLockInner, this::tryReadLockInner));
			writeLockMonitoring = LockMonitoring
					.wrap(Lock.ExtendedSupplier.wrap(this::createWriteLockInner, this::tryWriteLockInner));

		} else if (releaseAbandoned && !detectStalledOrDeadlock) {

			readLockMonitoring = new LockCleaner<>(
					stackTrace,
					alias + "_READ",
					LoggerFactory.getLogger(this.getClass()),
					Lock.ExtendedSupplier.wrap(this::createReadLockInner, this::tryReadLockInner)
			);

			writeLockMonitoring = new LockCleaner<>(
					stackTrace,
					alias + "_WRITE",
					LoggerFactory.getLogger(this.getClass()),
					Lock.ExtendedSupplier.wrap(this::createWriteLockInner, this::tryWriteLockInner)
			);

		} else {

			readLockMonitoring = new LockTracking<>(
					stackTrace,
					alias + "_READ",
					LoggerFactory.getLogger(this.getClass()),
					waitToCollect,
					Lock.ExtendedSupplier.wrap(this::createReadLockInner, this::tryReadLockInner)
			);

			writeLockMonitoring = new LockTracking<>(
					stackTrace,
					alias + "_WRITE",
					LoggerFactory.getLogger(this.getClass()),
					waitToCollect,
					Lock.ExtendedSupplier.wrap(this::createWriteLockInner, this::tryWriteLockInner)
			);
		}
	}

	abstract int getWriterPreference();

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean isWriterActive() {
		return stampedLock.isWriteLocked();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean isReaderActive() {
		long unlockedSum = readersUnlocked.sum();
		long lockedSum = readersLocked.sum();
		return unlockedSum != lockedSum;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void waitForActiveWriter() throws InterruptedException {
		while (stampedLock.isWriteLocked() && !isReaderActive()) {
			spinWait();
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void waitForActiveReaders() throws InterruptedException {
		while (isReaderActive()) {
			spinWait();
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Lock getReadLock() throws InterruptedException {
		return readLockMonitoring.getLock();
	}

	ReadLock createReadLockInner() throws InterruptedException {

		readersLocked.increment();
		while (stampedLock.isWriteLocked()) {
			try {
				spinWaitAtReadLock();
			} catch (InterruptedException e) {
				readersUnlocked.increment();
				throw e;
			}
		}

		return new ReadLock(readersUnlocked);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Lock getWriteLock() throws InterruptedException {
		return writeLockMonitoring.getLock();
	}

	private WriteLock createWriteLockInner() throws InterruptedException {

		// Acquire a write-lock.
		long writeStamp = writeLockInterruptibly();
		boolean lockAcquired = false;

		try {
			int attempts = 0;

			// Wait for active readers to finish.
			do {

				if (Thread.interrupted()) {
					throw new InterruptedException();
				}

				// The order is important here.
				long unlockedSum = readersUnlocked.sum();
				long lockedSum = readersLocked.sum();
				if (unlockedSum == lockedSum) {
					// No active readers.
					lockAcquired = true;
				} else {

					// If a thread is allowed to acquire more than one read-lock then we could deadlock if we keep
					// holding the write-lock while we wait for all readers to finish. This is because no read-locks can
					// be acquired while the write-lock is locked.
					if (attempts++ > writePreference) {
						attempts = 0;

						stampedLock.unlockWrite(writeStamp);
						writeStamp = 0;

						yieldWait();

						writeStamp = writeLockInterruptibly();
					} else {
						spinWait();
					}

				}

			} while (!lockAcquired);
		} finally {
			if (!lockAcquired && writeStamp != 0) {
				stampedLock.unlockWrite(writeStamp);
				writeStamp = 0;
			}
		}

		VarHandle.releaseFence();
		return new WriteLock(stampedLock, writeStamp);
	}

	private long writeLockInterruptibly() throws InterruptedException {

		if (writeLockMonitoring.requiresManualCleanup()) {
			long writeStamp;
			do {
				if (Thread.interrupted()) {
					throw new InterruptedException();
				}

				writeStamp = stampedLock.tryWriteLock(tryWriteLockMillis, TimeUnit.MILLISECONDS);

				if (writeStamp == 0) {

					writeLockMonitoring.runCleanup();
					readLockMonitoring.runCleanup();
				}
			} while (writeStamp == 0);
			return writeStamp;
		} else {
			return stampedLock.writeLockInterruptibly();
		}

	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Lock tryReadLock() {
		return readLockMonitoring.tryLock();
	}

	private ReadLock tryReadLockInner() {
		readersLocked.increment();
		if (!stampedLock.isWriteLocked()) {
			// Everything is good! We have acquired a read-lock and there are no active writers.
			return new ReadLock(readersUnlocked);
		} else {
			// There are active writers release our read lock
			readersUnlocked.increment();

			readLockMonitoring.runCleanup();
			writeLockMonitoring.runCleanup();
			return null;
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Lock tryWriteLock() {
		return writeLockMonitoring.tryLock();
	}

	private WriteLock tryWriteLockInner() {
		// Try to acquire a write-lock.
		long writeStamp = stampedLock.tryWriteLock();

		if (writeStamp != 0) {

			// The order is important here.
			long unlockedSum = readersUnlocked.sum();
			long lockedSum = readersLocked.sum();
			if (unlockedSum == lockedSum) {
				// No active readers.
				VarHandle.releaseFence();
				return new WriteLock(stampedLock, writeStamp);
			} else {
				stampedLock.unlockWrite(writeStamp);

				readLockMonitoring.runCleanup();
				writeLockMonitoring.runCleanup();
			}
		}

		return null;
	}

	void spinWait() throws InterruptedException {
		Thread.onSpinWait();

		writeLockMonitoring.runCleanup();
		readLockMonitoring.runCleanup();

		if (Thread.interrupted()) {
			throw new InterruptedException();
		}

	}

	void spinWaitAtReadLock() throws InterruptedException {
		Thread.onSpinWait();

		writeLockMonitoring.runCleanup();

		if (Thread.interrupted()) {
			throw new InterruptedException();
		}

	}

	private void yieldWait() throws InterruptedException {
		Thread.yield();

		writeLockMonitoring.runCleanup();
		readLockMonitoring.runCleanup();

		if (Thread.interrupted()) {
			throw new InterruptedException();
		}

	}

	static class WriteLock implements Lock {

		private final StampedLock lock;
		private long stamp;

		public WriteLock(StampedLock lock, long stamp) {
			assert stamp != 0;
			this.lock = lock;
			this.stamp = stamp;
		}

		@Override
		public boolean isActive() {
			return stamp != 0;
		}

		@Override
		public void release() {
			long temp = stamp;
			stamp = 0;

			if (temp == 0) {
				throw new IllegalMonitorStateException("Trying to release a lock that is not locked");
			}

			lock.unlockWrite(temp);
		}
	}

	static class ReadLock implements Lock {

		private final LongAdder readersUnlocked;
		private boolean locked = true;

		public ReadLock(LongAdder readersUnlocked) {
			this.readersUnlocked = readersUnlocked;
		}

		@Override
		public boolean isActive() {
			return locked;
		}

		@Override
		public void release() {
			if (!locked) {
				throw new IllegalMonitorStateException("Trying to release a lock that is not locked");
			}

			VarHandle.acquireFence();
			locked = false;
			readersUnlocked.increment();

		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy