/**
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.corfudb.infrastructure.log;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Allows acquiring different read/write locks for different addresses
*
* Created by Konstantin Spirov on 1/22/2015
*/
public class MultiReadWriteLock {
// all used locks
private ConcurrentHashMap<Long, LockReference> locks = new ConcurrentHashMap<>();
// lock references per thread
private final ThreadLocal<LinkedList<LockMetadata>> threadLockReferences = new ThreadLocal<>();
/**
* Acquire a read lock. The recommended use of this method is in try-with-resources statement.
* @param address id of the lock to acquire.
* @return A closable that will free the allocations for this lock if necessary
*/
public AutoCloseableLock acquireReadLock(final Long address) {
registerLockReference(address, false);
ReentrantReadWriteLock lock = constructLockFor(address);
final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
readLock.lock();
AtomicBoolean closed = new AtomicBoolean(false);
return () -> {
if (!closed.getAndSet(true)) {
try {
readLock.unlock();
clearEventuallyLockFor(address);
} finally {
deregisterLockReference(address, false);
}
}
};
}
/**
* Acquire a write lock. The recommended use of this method is in try-with-resources statement.
* @param address id of the lock to acquire.
* @return A closable that will free the allocations for this lock if necessary
*/
public AutoCloseableLock acquireWriteLock(final Long address) {
registerLockReference(address, true);
ReentrantReadWriteLock lock = constructLockFor(address);
final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
writeLock.lock();
AtomicBoolean closed = new AtomicBoolean(false);
return () -> {
if (!closed.getAndSet(true)) {
try {
writeLock.unlock();
clearEventuallyLockFor(address);
} finally {
deregisterLockReference(address, true);
}
}
};
}
private ReentrantReadWriteLock constructLockFor(Long name) {
return locks.compute(name, (key, ref) -> {
if (ref == null) {
ref = new LockReference(new ReentrantReadWriteLock());
}
ref.referenceCount++;
return ref;
}
).getLock();
}
private void clearEventuallyLockFor(Long name) {
locks.compute(name, (aLong, ref) -> {
if (ref == null) {
throw new IllegalStateException("Lock is wrongly used " + ref+" "+System.identityHashCode(Thread.currentThread()));
}
ref.referenceCount--;
if (ref.getReferenceCount()==0) {
return null;
} else {
return ref;
}
});
}
private void registerLockReference(long address, boolean writeLock) {
LinkedList<LockMetadata> threadLocks = threadLockReferences.get();
if (threadLocks==null) {
threadLocks = new LinkedList<>();
threadLockReferences.set(threadLocks);
} else {
LockMetadata last = threadLocks.getLast();
if (last.getAddress()>address) {
throw new IllegalStateException("Wrong lock acquisition order "+last.getAddress()+" > "+address);
}
if (writeLock) {
if (last.getAddress()==address && !last.isWriteLock()) {
throw new IllegalStateException("Write lock in the scope of read lock for "+address);
}
}
}
threadLocks.add(new LockMetadata(address, writeLock));
}
private void deregisterLockReference(long address, boolean writeLock) {
LinkedList<LockMetadata> threadLocks = threadLockReferences.get();
LockMetadata last = threadLocks.removeLast();
if (last.getAddress() != address || last.writeLock != writeLock) {
throw new IllegalStateException("Wrong unlocking order");
}
if (threadLocks.isEmpty()) {
threadLockReferences.set(null);
}
}
@Data
@AllArgsConstructor
private class LockMetadata {
private long address;
private boolean writeLock;
}
@Data
private class LockReference {
public LockReference(ReentrantReadWriteLock lock) {
this.lock = lock;
}
private ReentrantReadWriteLock lock;
private int referenceCount;
}
public interface AutoCloseableLock extends AutoCloseable {
@Override
void close();
}
}