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

org.redisson.RedissonLock Maven / Gradle / Ivy

There is a newer version: 3.40.2
Show newest version
/**
 * Copyright (c) 2013-2019 Nikita Koksharov
 *
 * 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
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.redisson;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;

import org.redisson.api.RFuture;
import org.redisson.api.RLock;
import org.redisson.client.RedisException;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommand.ValueType;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.RedisStrictCommand;
import org.redisson.client.protocol.convertor.IntegerReplayConvertor;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import org.redisson.pubsub.LockPubSub;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.netty.util.Timeout;
import io.netty.util.TimerTask;

/**
 * Distributed implementation of {@link java.util.concurrent.locks.Lock}
 * Implements reentrant lock.
* Lock will be removed automatically if client disconnects. *

* Implements a non-fair locking so doesn't guarantees an acquire order. * * @author Nikita Koksharov * */ public class RedissonLock extends RedissonExpirable implements RLock { public static class ExpirationEntry { private final Map threadIds = new LinkedHashMap<>(); private volatile Timeout timeout; public ExpirationEntry() { super(); } public void addThreadId(long threadId) { Integer counter = threadIds.get(threadId); if (counter == null) { counter = 1; } else { counter++; } threadIds.put(threadId, counter); } public boolean hasNoThreads() { return threadIds.isEmpty(); } public Long getFirstThreadId() { if (threadIds.isEmpty()) { return null; } return threadIds.keySet().iterator().next(); } public void removeThreadId(long threadId) { Integer counter = threadIds.get(threadId); if (counter == null) { return; } counter--; if (counter == 0) { threadIds.remove(threadId); } else { threadIds.put(threadId, counter); } } public void setTimeout(Timeout timeout) { this.timeout = timeout; } public Timeout getTimeout() { return timeout; } } private static final Logger log = LoggerFactory.getLogger(RedissonLock.class); private static final ConcurrentMap EXPIRATION_RENEWAL_MAP = new ConcurrentHashMap<>(); protected long internalLockLeaseTime; final UUID id; final String entryName; protected final LockPubSub pubSub; final CommandAsyncExecutor commandExecutor; public RedissonLock(CommandAsyncExecutor commandExecutor, String name) { super(commandExecutor, name); this.commandExecutor = commandExecutor; this.id = commandExecutor.getConnectionManager().getId(); this.internalLockLeaseTime = commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(); this.entryName = id + ":" + name; this.pubSub = commandExecutor.getConnectionManager().getSubscribeService().getLockPubSub(); } protected String getEntryName() { return entryName; } String getChannelName() { return prefixName("redisson_lock__channel", getName()); } protected String getLockName(long threadId) { return id + ":" + threadId; } @Override public void lock() { try { lock(-1, null, false); } catch (InterruptedException e) { throw new IllegalStateException(); } } @Override public void lock(long leaseTime, TimeUnit unit) { try { lock(leaseTime, unit, false); } catch (InterruptedException e) { throw new IllegalStateException(); } } @Override public void lockInterruptibly() throws InterruptedException { lock(-1, null, true); } @Override public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException { lock(leaseTime, unit, true); } private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException { long threadId = Thread.currentThread().getId(); Long ttl = tryAcquire(leaseTime, unit, threadId); // lock acquired if (ttl == null) { return; } RFuture future = subscribe(threadId); commandExecutor.syncSubscription(future); try { while (true) { ttl = tryAcquire(leaseTime, unit, threadId); // lock acquired if (ttl == null) { break; } // waiting for message if (ttl >= 0) { try { getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { if (interruptibly) { throw e; } getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } } else { if (interruptibly) { getEntry(threadId).getLatch().acquire(); } else { getEntry(threadId).getLatch().acquireUninterruptibly(); } } } } finally { unsubscribe(future, threadId); } // get(lockAsync(leaseTime, unit)); } private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) { return get(tryAcquireAsync(leaseTime, unit, threadId)); } private RFuture tryAcquireOnceAsync(long leaseTime, TimeUnit unit, long threadId) { if (leaseTime != -1) { return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN); } RFuture ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN); ttlRemainingFuture.onComplete((ttlRemaining, e) -> { if (e != null) { return; } // lock acquired if (ttlRemaining) { scheduleExpirationRenewal(threadId); } }); return ttlRemainingFuture; } private RFuture tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) { if (leaseTime != -1) { return tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_LONG); } RFuture ttlRemainingFuture = tryLockInnerAsync(commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG); ttlRemainingFuture.onComplete((ttlRemaining, e) -> { if (e != null) { return; } // lock acquired if (ttlRemaining == null) { scheduleExpirationRenewal(threadId); } }); return ttlRemainingFuture; } @Override public boolean tryLock() { return get(tryLockAsync()); } private void renewExpiration() { ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ee == null) { return; } Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ent == null) { return; } Long threadId = ent.getFirstThreadId(); if (threadId == null) { return; } RFuture future = renewExpirationAsync(threadId); future.onComplete((res, e) -> { if (e != null) { log.error("Can't update lock " + getName() + " expiration", e); return; } if (res) { // reschedule itself renewExpiration(); } }); } }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); ee.setTimeout(task); } private void scheduleExpirationRenewal(long threadId) { ExpirationEntry entry = new ExpirationEntry(); ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry); if (oldEntry != null) { oldEntry.addThreadId(threadId); } else { entry.addThreadId(threadId); renewExpiration(); } } protected RFuture renewExpirationAsync(long threadId) { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return 1; " + "end; " + "return 0;", Collections.singletonList(getName()), internalLockLeaseTime, getLockName(threadId)); } void cancelExpirationRenewal(Long threadId) { ExpirationEntry task = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (task == null) { return; } if (threadId != null) { task.removeThreadId(threadId); } if (threadId == null || task.hasNoThreads()) { task.getTimeout().cancel(); EXPIRATION_RENEWAL_MAP.remove(getEntryName()); } } RFuture tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand command) { internalLockLeaseTime = unit.toMillis(leaseTime); return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command, "if (redis.call('exists', KEYS[1]) == 0) then " + "redis.call('hset', KEYS[1], ARGV[2], 1); " + "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return nil; " + "end; " + "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " + "redis.call('hincrby', KEYS[1], ARGV[2], 1); " + "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return nil; " + "end; " + "return redis.call('pttl', KEYS[1]);", Collections.singletonList(getName()), internalLockLeaseTime, getLockName(threadId)); } private void acquireFailed(long threadId) { get(acquireFailedAsync(threadId)); } protected RFuture acquireFailedAsync(long threadId) { return RedissonPromise.newSucceededFuture(null); } @Override public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException { long time = unit.toMillis(waitTime); long current = System.currentTimeMillis(); long threadId = Thread.currentThread().getId(); Long ttl = tryAcquire(leaseTime, unit, threadId); // lock acquired if (ttl == null) { return true; } time -= System.currentTimeMillis() - current; if (time <= 0) { acquireFailed(threadId); return false; } current = System.currentTimeMillis(); RFuture subscribeFuture = subscribe(threadId); if (!await(subscribeFuture, time, TimeUnit.MILLISECONDS)) { if (!subscribeFuture.cancel(false)) { subscribeFuture.onComplete((res, e) -> { if (e == null) { unsubscribe(subscribeFuture, threadId); } }); } acquireFailed(threadId); return false; } try { time -= System.currentTimeMillis() - current; if (time <= 0) { acquireFailed(threadId); return false; } while (true) { long currentTime = System.currentTimeMillis(); ttl = tryAcquire(leaseTime, unit, threadId); // lock acquired if (ttl == null) { return true; } time -= System.currentTimeMillis() - currentTime; if (time <= 0) { acquireFailed(threadId); return false; } // waiting for message currentTime = System.currentTimeMillis(); if (ttl >= 0 && ttl < time) { getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS); } else { getEntry(threadId).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS); } time -= System.currentTimeMillis() - currentTime; if (time <= 0) { acquireFailed(threadId); return false; } } } finally { unsubscribe(subscribeFuture, threadId); } // return get(tryLockAsync(waitTime, leaseTime, unit)); } protected RedissonLockEntry getEntry(long threadId) { return pubSub.getEntry(getEntryName()); } protected RFuture subscribe(long threadId) { return pubSub.subscribe(getEntryName(), getChannelName()); } protected void unsubscribe(RFuture future, long threadId) { pubSub.unsubscribe(future.getNow(), getEntryName(), getChannelName()); } @Override public boolean tryLock(long waitTime, TimeUnit unit) throws InterruptedException { return tryLock(waitTime, -1, unit); } @Override public void unlock() { try { get(unlockAsync(Thread.currentThread().getId())); } catch (RedisException e) { if (e.getCause() instanceof IllegalMonitorStateException) { throw (IllegalMonitorStateException) e.getCause(); } else { throw e; } } // Future future = unlockAsync(); // future.awaitUninterruptibly(); // if (future.isSuccess()) { // return; // } // if (future.cause() instanceof IllegalMonitorStateException) { // throw (IllegalMonitorStateException)future.cause(); // } // throw commandExecutor.convertException(future); } @Override public Condition newCondition() { // TODO implement throw new UnsupportedOperationException(); } @Override public boolean forceUnlock() { return get(forceUnlockAsync()); } @Override public RFuture forceUnlockAsync() { cancelExpirationRenewal(null); return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('del', KEYS[1]) == 1) then " + "redis.call('publish', KEYS[2], ARGV[1]); " + "return 1 " + "else " + "return 0 " + "end", Arrays.asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE); } @Override public boolean isLocked() { return isExists(); } @Override public RFuture isLockedAsync() { return isExistsAsync(); } @Override public RFuture isExistsAsync() { return commandExecutor.writeAsync(getName(), codec, RedisCommands.EXISTS, getName()); } @Override public boolean isHeldByCurrentThread() { return isHeldByThread(Thread.currentThread().getId()); } @Override public boolean isHeldByThread(long threadId) { RFuture future = commandExecutor.writeAsync(getName(), LongCodec.INSTANCE, RedisCommands.HEXISTS, getName(), getLockName(threadId)); return get(future); } private static final RedisCommand HGET = new RedisCommand("HGET", ValueType.MAP_VALUE, new IntegerReplayConvertor(0)); public RFuture getHoldCountAsync() { return commandExecutor.writeAsync(getName(), LongCodec.INSTANCE, HGET, getName(), getLockName(Thread.currentThread().getId())); } @Override public int getHoldCount() { return get(getHoldCountAsync()); } @Override public RFuture deleteAsync() { return forceUnlockAsync(); } @Override public RFuture unlockAsync() { long threadId = Thread.currentThread().getId(); return unlockAsync(threadId); } protected RFuture unlockInnerAsync(long threadId) { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " + "return nil;" + "end; " + "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " + "if (counter > 0) then " + "redis.call('pexpire', KEYS[1], ARGV[2]); " + "return 0; " + "else " + "redis.call('del', KEYS[1]); " + "redis.call('publish', KEYS[2], ARGV[1]); " + "return 1; "+ "end; " + "return nil;", Arrays.asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId)); } @Override public RFuture unlockAsync(long threadId) { RPromise result = new RedissonPromise(); RFuture future = unlockInnerAsync(threadId); future.onComplete((opStatus, e) -> { if (e != null) { cancelExpirationRenewal(threadId); result.tryFailure(e); return; } if (opStatus == null) { IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + id + " thread-id: " + threadId); result.tryFailure(cause); return; } cancelExpirationRenewal(threadId); result.trySuccess(null); }); return result; } @Override public RFuture lockAsync() { return lockAsync(-1, null); } @Override public RFuture lockAsync(long leaseTime, TimeUnit unit) { long currentThreadId = Thread.currentThread().getId(); return lockAsync(leaseTime, unit, currentThreadId); } @Override public RFuture lockAsync(long currentThreadId) { return lockAsync(-1, null, currentThreadId); } @Override public RFuture lockAsync(long leaseTime, TimeUnit unit, long currentThreadId) { RPromise result = new RedissonPromise(); RFuture ttlFuture = tryAcquireAsync(leaseTime, unit, currentThreadId); ttlFuture.onComplete((ttl, e) -> { if (e != null) { result.tryFailure(e); return; } // lock acquired if (ttl == null) { if (!result.trySuccess(null)) { unlockAsync(currentThreadId); } return; } RFuture subscribeFuture = subscribe(currentThreadId); subscribeFuture.onComplete((res, ex) -> { if (ex != null) { result.tryFailure(ex); return; } lockAsync(leaseTime, unit, subscribeFuture, result, currentThreadId); }); }); return result; } private void lockAsync(long leaseTime, TimeUnit unit, RFuture subscribeFuture, RPromise result, long currentThreadId) { RFuture ttlFuture = tryAcquireAsync(leaseTime, unit, currentThreadId); ttlFuture.onComplete((ttl, e) -> { if (e != null) { unsubscribe(subscribeFuture, currentThreadId); result.tryFailure(e); return; } // lock acquired if (ttl == null) { unsubscribe(subscribeFuture, currentThreadId); if (!result.trySuccess(null)) { unlockAsync(currentThreadId); } return; } RedissonLockEntry entry = getEntry(currentThreadId); if (entry.getLatch().tryAcquire()) { lockAsync(leaseTime, unit, subscribeFuture, result, currentThreadId); } else { // waiting for message AtomicReference futureRef = new AtomicReference(); Runnable listener = () -> { if (futureRef.get() != null) { futureRef.get().cancel(); } lockAsync(leaseTime, unit, subscribeFuture, result, currentThreadId); }; entry.addListener(listener); if (ttl >= 0) { Timeout scheduledFuture = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { if (entry.removeListener(listener)) { lockAsync(leaseTime, unit, subscribeFuture, result, currentThreadId); } } }, ttl, TimeUnit.MILLISECONDS); futureRef.set(scheduledFuture); } } }); } @Override public RFuture tryLockAsync() { return tryLockAsync(Thread.currentThread().getId()); } @Override public RFuture tryLockAsync(long threadId) { return tryAcquireOnceAsync(-1, null, threadId); } @Override public RFuture tryLockAsync(long waitTime, TimeUnit unit) { return tryLockAsync(waitTime, -1, unit); } @Override public RFuture tryLockAsync(long waitTime, long leaseTime, TimeUnit unit) { long currentThreadId = Thread.currentThread().getId(); return tryLockAsync(waitTime, leaseTime, unit, currentThreadId); } @Override public RFuture tryLockAsync(long waitTime, long leaseTime, TimeUnit unit, long currentThreadId) { RPromise result = new RedissonPromise(); AtomicLong time = new AtomicLong(unit.toMillis(waitTime)); long currentTime = System.currentTimeMillis(); RFuture ttlFuture = tryAcquireAsync(leaseTime, unit, currentThreadId); ttlFuture.onComplete((ttl, e) -> { if (e != null) { result.tryFailure(e); return; } // lock acquired if (ttl == null) { if (!result.trySuccess(true)) { unlockAsync(currentThreadId); } return; } long el = System.currentTimeMillis() - currentTime; time.addAndGet(-el); if (time.get() <= 0) { trySuccessFalse(currentThreadId, result); return; } long current = System.currentTimeMillis(); AtomicReference futureRef = new AtomicReference(); RFuture subscribeFuture = subscribe(currentThreadId); subscribeFuture.onComplete((r, ex) -> { if (ex != null) { result.tryFailure(ex); return; } if (futureRef.get() != null) { futureRef.get().cancel(); } long elapsed = System.currentTimeMillis() - current; time.addAndGet(-elapsed); tryLockAsync(time, leaseTime, unit, subscribeFuture, result, currentThreadId); }); if (!subscribeFuture.isDone()) { Timeout scheduledFuture = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { if (!subscribeFuture.isDone()) { subscribeFuture.cancel(false); trySuccessFalse(currentThreadId, result); } } }, time.get(), TimeUnit.MILLISECONDS); futureRef.set(scheduledFuture); } }); return result; } private void trySuccessFalse(long currentThreadId, RPromise result) { acquireFailedAsync(currentThreadId).onComplete((res, e) -> { if (e == null) { result.trySuccess(false); } else { result.tryFailure(e); } }); } private void tryLockAsync(AtomicLong time, long leaseTime, TimeUnit unit, RFuture subscribeFuture, RPromise result, long currentThreadId) { if (result.isDone()) { unsubscribe(subscribeFuture, currentThreadId); return; } if (time.get() <= 0) { unsubscribe(subscribeFuture, currentThreadId); trySuccessFalse(currentThreadId, result); return; } long curr = System.currentTimeMillis(); RFuture ttlFuture = tryAcquireAsync(leaseTime, unit, currentThreadId); ttlFuture.onComplete((ttl, e) -> { if (e != null) { unsubscribe(subscribeFuture, currentThreadId); result.tryFailure(e); return; } // lock acquired if (ttl == null) { unsubscribe(subscribeFuture, currentThreadId); if (!result.trySuccess(true)) { unlockAsync(currentThreadId); } return; } long el = System.currentTimeMillis() - curr; time.addAndGet(-el); if (time.get() <= 0) { unsubscribe(subscribeFuture, currentThreadId); trySuccessFalse(currentThreadId, result); return; } // waiting for message long current = System.currentTimeMillis(); RedissonLockEntry entry = getEntry(currentThreadId); if (entry.getLatch().tryAcquire()) { tryLockAsync(time, leaseTime, unit, subscribeFuture, result, currentThreadId); } else { AtomicBoolean executed = new AtomicBoolean(); AtomicReference futureRef = new AtomicReference(); Runnable listener = () -> { executed.set(true); if (futureRef.get() != null) { futureRef.get().cancel(); } long elapsed = System.currentTimeMillis() - current; time.addAndGet(-elapsed); tryLockAsync(time, leaseTime, unit, subscribeFuture, result, currentThreadId); }; entry.addListener(listener); long t = time.get(); if (ttl >= 0 && ttl < time.get()) { t = ttl; } if (!executed.get()) { Timeout scheduledFuture = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { if (entry.removeListener(listener)) { long elapsed = System.currentTimeMillis() - current; time.addAndGet(-elapsed); tryLockAsync(time, leaseTime, unit, subscribeFuture, result, currentThreadId); } } }, t, TimeUnit.MILLISECONDS); futureRef.set(scheduledFuture); } } }); } }