LockCleaner.java

/*******************************************************************************
 * 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.diagnostics;

import java.lang.ref.Cleaner;

import org.eclipse.rdf4j.common.annotation.InternalUseOnly;
import org.eclipse.rdf4j.common.concurrent.locks.Lock;
import org.eclipse.rdf4j.common.concurrent.locks.Properties;
import org.slf4j.Logger;

/**
 * Automatically log and release locks that are no longer referenced and will be garbage collected.
 *
 * @author H��vard M. Ottestad
 */
@InternalUseOnly
public class LockCleaner<T extends Lock> implements LockMonitoring<T> {

	private final static ConcurrentCleaner cleaner = new ConcurrentCleaner();
	private final Logger logger;
	private final Lock.ExtendedSupplier<T> supplier;
	private final String alias;
	private final boolean stacktrace;

	public LockCleaner(boolean stacktrace, String alias, Logger logger, Lock.ExtendedSupplier<T> supplier) {
		this.stacktrace = stacktrace;
		this.supplier = supplier;
		this.logger = logger;
		this.alias = alias;
	}

	public Lock getLock() throws InterruptedException {
		return getLockInner(supplier.getLock());
	}

	@Override
	public Lock tryLock() {

		T lock = supplier.tryLock();
		if (lock != null) {
			return getLockInner(lock);
		}

		return null;
	}

	@Override
	public T unsafeInnerLock(Lock lock) {
		if (lock instanceof CleanableLock) {
			return ((CleanableLock<T>) lock).state.lock;
		} else {
			throw new IllegalArgumentException("Supplied lock is not instanceof CleanableLock");
		}
	}

	@Override
	public Lock register(T lock) {
		return getLockInner(lock);
	}

	@Override
	public void unregister(Lock lock) {
		assert !lock.isActive();
		if (lock instanceof CleanableLock) {
			((CleanableLock<T>) lock).cleanable.clean();
		} else {
			throw new IllegalArgumentException("Supplied lock is not instanceof CleanableLock");
		}

	}

	private CleanableLock<T> getLockInner(T lock) {
		if (stacktrace) {
			return new CleanableLock<>(cleaner, lock, alias, logger, Thread.currentThread(),
					new Throwable("\"" + alias + "\" lock acquired in " + Thread.currentThread().getName()));
		} else {
			return new CleanableLock<>(cleaner, lock, alias, logger, null, null);
		}
	}

	public static class CleanableLock<T extends Lock> implements Lock {

		private final Cleaner.Cleanable cleanable;
		private final State<T> state;

		public CleanableLock(ConcurrentCleaner cleaner, T lock, String alias, Logger logger, Thread thread,
				Throwable throwable) {
			this.state = new State<>(lock, alias, logger, thread, throwable);
			this.cleanable = cleaner.register(this, state);
		}

		@Override
		public boolean isActive() {
			return state.lock.isActive();
		}

		@Override
		public void release() {
			state.lock.release();
			cleanable.clean();
		}

		static class State<T extends Lock> implements Runnable {

			private final T lock;
			private final String alias;
			private final Logger logger;
			private final Thread thread;
			private final Throwable stack;

			public State(T lock, String alias, Logger logger, Thread thread, Throwable stack) {
				this.lock = lock;
				this.alias = alias;
				this.logger = logger;
				this.thread = thread;
				this.stack = stack;
			}

			public void run() {
				// cleanup action accessing State, executed at most once
				if (lock.isActive()) {

					if (stack == null) {
						logger.warn("\"{}\" lock abandoned; consider setting the {} system property", alias,
								Properties.TRACK_LOCKS);
					} else {
						logger.warn("\"{}\" lock abandoned; lock was acquired in {}", alias, thread.getName(), stack);
					}

					lock.release();

				}
			}

		}

	}
}