Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.infinispan.util.concurrent.locks.impl.InfinispanLock Maven / Gradle / Ivy
package org.infinispan.util.concurrent.locks.impl;
import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater;
import static org.infinispan.util.concurrent.CompletableFutures.await;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import org.infinispan.util.TimeService;
import org.infinispan.util.concurrent.TimeoutException;
import org.infinispan.util.concurrent.locks.DeadlockChecker;
import org.infinispan.util.concurrent.locks.DeadlockDetectedException;
import org.infinispan.util.concurrent.locks.ExtendedLockPromise;
import org.infinispan.util.concurrent.locks.LockListener;
import org.infinispan.util.concurrent.locks.LockState;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;
/**
* A special lock for Infinispan cache.
*
* The main different with the traditional {@link java.util.concurrent.locks.Lock} is allowing to use any object as lock
* owner. It is possible to use a {@link Thread} as lock owner that makes similar to {@link
* java.util.concurrent.locks.Lock}.
*
* In addition, it has an asynchronous interface. {@link #acquire(Object, long, TimeUnit)} will not acquire the lock
* immediately (except if it is free) but will return a {@link ExtendedLockPromise}. This promise allow to test if the
* lock is acquired asynchronously and cancel the lock acquisition, without any blocking.
*
* @author Pedro Ruivo
* @since 8.0
*/
public class InfinispanLock {
private static final Log log = LogFactory.getLog(InfinispanLock.class);
private static final boolean trace = log.isTraceEnabled();
private static final AtomicReferenceFieldUpdater OWNER_UPDATER =
newUpdater(InfinispanLock.class, LockPlaceHolder.class, "current");
private static final AtomicReferenceFieldUpdater STATE_UPDATER =
newUpdater(LockPlaceHolder.class, LockState.class, "lockState");
private final Queue pendingRequest;
private final ConcurrentMap lockOwners;
private final Runnable releaseRunnable;
private TimeService timeService;
private volatile LockPlaceHolder current;
/**
* Creates a new instance.
*
* @param timeService the {@link TimeService} to check for timeouts.
*/
public InfinispanLock(TimeService timeService) {
this.timeService = timeService;
pendingRequest = new ConcurrentLinkedQueue<>();
lockOwners = new ConcurrentHashMap<>();
current = null;
releaseRunnable = null;
}
/**
* Creates a new instance.
*
* @param timeService the {@link TimeService} to check for timeouts.
* @param releaseRunnable a {@link Runnable} that is invoked every time this lock is released.
*/
public InfinispanLock(TimeService timeService, Runnable releaseRunnable) {
this.timeService = timeService;
pendingRequest = new ConcurrentLinkedQueue<>();
lockOwners = new ConcurrentHashMap<>();
current = null;
this.releaseRunnable = releaseRunnable;
}
/**
* Tests purpose only!
*/
public void setTimeService(TimeService timeService) {
if (timeService != null) {
this.timeService = timeService;
}
}
/**
* It tries to acquire this lock.
*
* If it is invoked multiple times with the same owner, the same {@link ExtendedLockPromise} is returned until it has
* timed-out or {@link #release(Object)} is invoked.
*
* If the lock is free, it is immediately acquired, otherwise the lock owner is queued.
*
* @param lockOwner the lock owner who needs to acquire the lock.
* @param time the timeout value.
* @param timeUnit the timeout unit.
* @return an {@link ExtendedLockPromise}.
* @throws NullPointerException if {@code lockOwner} or {@code timeUnit} is {@code null}.
*/
public ExtendedLockPromise acquire(Object lockOwner, long time, TimeUnit timeUnit) {
Objects.requireNonNull(lockOwner, "Lock Owner should be non-null");
Objects.requireNonNull(timeUnit, "Time Unit should be non-null");
if (trace) {
log.tracef("Acquire lock for %s. Timeout=%s (%s)", lockOwner, time, timeUnit);
}
LockPlaceHolder lockPlaceHolder = lockOwners.get(lockOwner);
if (lockPlaceHolder != null) {
if (trace) {
log.tracef("Lock owner already exists: %s", lockPlaceHolder);
}
return lockPlaceHolder;
}
lockPlaceHolder = createLockInfo(lockOwner, time, timeUnit);
LockPlaceHolder other = lockOwners.putIfAbsent(lockOwner, lockPlaceHolder);
if (other != null) {
if (trace) {
log.tracef("Lock owner already exists: %s", other);
}
return other;
}
if (trace) {
log.tracef("Created a new one: %s", lockPlaceHolder);
}
pendingRequest.add(lockPlaceHolder);
tryAcquire(null);
return lockPlaceHolder;
}
/**
* It tries to release the lock held by {@code lockOwner}.
*
* If the lock is not acquired (is waiting or timed out/deadlocked) by {@code lockOwner}, its {@link
* ExtendedLockPromise} is canceled. If {@code lockOwner} is the current lock owner, the lock is released and the
* next lock owner available will acquire the lock. If the {@code lockOwner} never tried to acquire the lock, this
* method does nothing.
*
* @param lockOwner the lock owner who wants to release the lock.
* @throws NullPointerException if {@code lockOwner} is {@code null}.
*/
public void release(Object lockOwner) {
Objects.requireNonNull(lockOwner, "Lock Owner should be non-null");
if (trace) {
log.tracef("Release lock for %s.", lockOwner);
}
LockPlaceHolder wantToRelease = lockOwners.get(lockOwner);
if (wantToRelease == null) {
if (trace) {
log.tracef("%s not found!", lockOwner);
}
//nothing to release
return;
}
final boolean released = wantToRelease.setReleased();
if (trace) {
log.tracef("Release lock for %s? %s", wantToRelease, released);
}
LockPlaceHolder currentLocked = current;
if (currentLocked == wantToRelease) {
tryAcquire(wantToRelease);
}
}
/**
* @return the current lock owner or {@code null} if it is not acquired.
*/
public Object getLockOwner() {
LockPlaceHolder lockPlaceHolder = current;
return lockPlaceHolder == null ? null : lockPlaceHolder.owner;
}
/**
* It checks if the lock is acquired.
*
* A {@code false} return value does not mean the lock is free since it may have queued lock owners.
*
* @return {@code true} if the lock is acquired.
*/
public boolean isLocked() {
return current != null;
}
/**
* It forces a deadlock checking.
*/
public void deadlockCheck(DeadlockChecker deadlockChecker) {
if (deadlockChecker == null) {
return; //no-op
}
LockPlaceHolder holder = current;
if (holder != null) {
for (LockPlaceHolder pending : pendingRequest) {
pending.checkDeadlock(deadlockChecker, holder.owner);
}
}
}
/**
* It tests if the lock has the lock owner.
*
* It return {@code true} if the lock owner is the current lock owner or it in the queue.
*
* @param lockOwner the lock owner to test.
* @return {@code true} if it contains the lock owner.
*/
public boolean containsLockOwner(Object lockOwner) {
return lockOwners.containsKey(lockOwner);
}
private void onCanceled(LockPlaceHolder canceled) {
if (trace) {
log.tracef("Release lock for %s. It was canceled.", canceled.owner);
}
LockPlaceHolder currentLocked = current;
if (currentLocked == canceled) {
tryAcquire(canceled);
}
}
private boolean casRelease(LockPlaceHolder lockPlaceHolder) {
return cas(lockPlaceHolder, null);
}
private boolean remove(Object lockOwner) {
return lockOwners.remove(lockOwner) != null;
}
private void triggerReleased() {
if (releaseRunnable != null) {
releaseRunnable.run();
}
}
private boolean cas(LockPlaceHolder release, LockPlaceHolder acquire) {
boolean cas = OWNER_UPDATER.compareAndSet(this, release, acquire);
if (trace) {
log.tracef("Lock Owner CAS(%s, %s) => %s", release, acquire, cas);
}
return cas;
}
private void tryAcquire(LockPlaceHolder release) {
LockPlaceHolder toRelease = release;
do {
LockPlaceHolder toAcquire = pendingRequest.peek();
if (trace) {
log.tracef("Try acquire. Next in queue=%s. Current=%s", toAcquire, current);
}
if (toAcquire == null && toRelease == null) {
return;
} else if (toAcquire == null) {
//nothing to acquire, but we have to release the current.
if (casRelease(toRelease)) {
toRelease = null;
continue; //in the meanwhile, we could have a new request. recheck!
}
return;
}
if (cas(toRelease, toAcquire)) {
//we set the current lock owner, so we must remove it from the queue
pendingRequest.remove(toAcquire);
if (toAcquire.setAcquire()) {
if (trace) {
log.tracef("%s successfully acquired the lock.", toAcquire);
}
return;
}
if (trace) {
log.tracef("%s failed to acquire (invalid state). Retrying.", toAcquire);
}
//oh oh, probably the nextPending Timed-Out. we are going to retry with the next in queue
toRelease = toAcquire;
} else {
if (trace) {
log.tracef("Unable to acquire. Lock is held.");
}
//other thread already set the current lock owner
return;
}
} while (true);
}
private LockPlaceHolder createLockInfo(Object lockOwner, long time, TimeUnit timeUnit) {
return new LockPlaceHolder(lockOwner, timeService.expectedEndTime(time, timeUnit));
}
private class LockPlaceHolder implements ExtendedLockPromise {
private final Object owner;
private final long timeout;
private final CompletableFuture notifier;
volatile LockState lockState;
private LockPlaceHolder(Object owner, long timeout) {
this.owner = owner;
this.timeout = timeout;
lockState = LockState.WAITING;
notifier = new CompletableFuture<>();
}
@Override
public boolean isAvailable() {
checkTimeout();
return lockState != LockState.WAITING;
}
@Override
public void lock() throws InterruptedException, TimeoutException {
do {
LockState currentState = lockState;
switch (currentState) {
case WAITING:
checkTimeout();
await(notifier, timeService.remainingTime(timeout, TimeUnit.NANOSECONDS), TimeUnit.NANOSECONDS);
break;
case ACQUIRED:
return; //acquired!
case RELEASED:
throw new IllegalStateException("Lock already released!");
case TIMED_OUT:
cleanup();
throw new TimeoutException("Timeout waiting for lock.");
case DEADLOCKED:
cleanup();
throw new DeadlockDetectedException("DeadLock detected");
default:
throw new IllegalStateException("Unknown lock state: " + currentState);
}
} while (true);
}
@Override
public void addListener(LockListener listener) {
notifier.thenRun(() -> this.invoke(listener));
}
@Override
public void cancel(LockState state) {
checkValidCancelState(state);
do {
LockState currentState = lockState;
switch (currentState) {
case WAITING:
if (casState(LockState.WAITING, state)) {
onCanceled(this);
notifyListeners();
return;
}
break;
case ACQUIRED: //no-op, a thread is inside the critical section.
case TIMED_OUT:
case DEADLOCKED:
case RELEASED:
return; //no-op, the lock is in final state.
default:
throw new IllegalStateException("Unknown lock state " + currentState);
}
} while (true);
}
@Override
public Object getRequestor() {
return owner;
}
@Override
public Object getOwner() {
LockPlaceHolder owner = current;
return owner != null ? owner.owner : null;
}
@Override
public String toString() {
return "LockPlaceHolder{" +
"lockState=" + lockState +
", owner=" + owner +
'}';
}
private void invoke(LockListener invoker) {
LockState state = lockState;
switch (state) {
case WAITING:
throw new IllegalStateException("WAITING is not a valid state to invoke the listener");
case ACQUIRED:
case RELEASED:
invoker.onEvent(LockState.ACQUIRED);
break;
case TIMED_OUT:
case DEADLOCKED:
invoker.onEvent(state);
break;
default:
throw new IllegalStateException("Unknown lock state " + state);
}
}
private void checkValidCancelState(LockState state) {
if (state != LockState.TIMED_OUT && state != LockState.DEADLOCKED) {
throw new IllegalArgumentException("LockState " + state + " is not valid to cancel.");
}
}
private void checkDeadlock(DeadlockChecker checker, Object currentOwner) {
checkTimeout(); //check timeout before checking the deadlock. check deadlock are more expensive.
if (lockState == LockState.WAITING && //we are waiting for a lock
!owner.equals(currentOwner) && //needed? just to be safe
checker.deadlockDetected(owner, currentOwner) && //deadlock has been detected!
casState(LockState.WAITING, LockState.DEADLOCKED)) { //state could have been changed to available or timed_out
onCanceled(this);
notifyListeners();
}
}
private boolean setAcquire() {
if (casState(LockState.WAITING, LockState.ACQUIRED)) {
notifyListeners();
}
return lockState == LockState.ACQUIRED;
}
private boolean setReleased() {
do {
LockState state = lockState;
switch (state) {
case WAITING:
if (casState(state, LockState.RELEASED)) {
cleanup();
notifyListeners();
return true;
}
break;
case ACQUIRED:
case TIMED_OUT:
case DEADLOCKED:
if (casState(state, LockState.RELEASED)) {
cleanup();
return true;
}
break;
case RELEASED:
return false;
default:
throw new IllegalStateException("Unknown lock state " + state);
}
} while (true);
}
private boolean casState(LockState expect, LockState update) {
boolean updated = STATE_UPDATER.compareAndSet(this, expect, update);
if (updated && trace) {
log.tracef("State changed for %s. %s => %s", this, expect, update);
}
return updated;
}
private void cleanup() {
if (remove(owner)) {
triggerReleased();
}
}
private void checkTimeout() {
if (lockState == LockState.WAITING &&
timeService.isTimeExpired(timeout) &&
casState(LockState.WAITING, LockState.TIMED_OUT)) {
onCanceled(this);
notifyListeners();
}
}
private void notifyListeners() {
if (lockState != LockState.WAITING) {
notifier.complete(null);
}
}
}
}