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.redisson.RedissonFasterMultiLock Maven / Gradle / Ivy
Go to download
Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC
/**
* Copyright (c) 2013-2024 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 io.netty.buffer.ByteBuf;
import io.netty.util.Timeout;
import org.redisson.api.RFuture;
import org.redisson.client.RedisTimeoutException;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.RedisStrictCommand;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.misc.CompletableFutureWrapper;
import org.redisson.misc.Hash;
import org.redisson.pubsub.LockPubSub;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
/**
* RedissonFasterMultiLock.
* All lock, unlock, lockAsync unlockAsync methods only success when all values locked succeed.
* Example:
* there is a class, id is 100, and three students in class, Jack(id:001),Mary(id:002)
*
* current thread id : 1
* ServiceManager id: 71b96ce8-2746......
* current time stamp: 1727422868000
*
* when {@code redissonBatchLock.lock("class_100",Arrays.asList("Jack_001","Mary_002")}
* It will be saved In redis like this:
*
* -----------------------------------------------------------------------
* | redis type: hash |
* | redis Key: class_100 |
* -----------------------------------------------------------------------
* | field | value |
* -----------------------------------------------------------------------
* | Jack_001 | 71b96ce8-2746:1 |
* | Mary_002 | 71b96ce8-2746:1 |
* | Jack_001:71b96ce8-2746:1:expire_time | 1,727,422,898,000 |
* | Jack_001:71b96ce8-2746:1:lock_count | 1 |
* | Mary_002:71b96ce8-2746:1:expire_time | 1,727,422,898,000 |
* | Mary_002:71b96ce8-2746:1:lock_count | 1 |
* -----------------------------------------------------------------------
*
* Attention: the value of group
should be `smallest`, in our example above ,
* group
should be 'class_100' not just 'class'
* Of course the values `Jack_001`,`Mary_002` will be encoded and hashed.
*
* @author lyrric
*
*/
public class RedissonFasterMultiLock extends RedissonBaseLock {
private static final Logger LOGGER = LoggerFactory.getLogger(RedissonFasterMultiLock.class);
protected long internalLockLeaseTime;
private final LockPubSub pubSub;
private final CommandAsyncExecutor commandExecutor;
private final String key;
private final Collection fields;
public RedissonFasterMultiLock(CommandAsyncExecutor commandExecutor, String group, Collection values) {
super(commandExecutor, group);
this.commandExecutor = commandExecutor;
this.pubSub = commandExecutor.getConnectionManager().getSubscribeService().getLockPubSub();
this.internalLockLeaseTime = getServiceManager().getCfg().getLockWatchdogTimeout();
this.key = group;
fields = hashValues(values);
}
private Collection hashValues(Collection values) {
return values.stream()
.map(this::hashValue)
.collect(Collectors.toSet());
}
@Override
public String getName() {
throw new UnsupportedOperationException();
}
@Override
public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {
lock(leaseTime, unit, true);
}
@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(waitTime, unit, threadId);
return false;
}
current = System.currentTimeMillis();
CompletableFuture subscribeFuture = subscribe();
try {
subscribeFuture.get(time, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
if (!subscribeFuture.completeExceptionally(new RedisTimeoutException(
"Unable to acquire subscription lock after " + time + "ms. " +
"Try to increase 'subscriptionsPerConnection' and/or 'subscriptionConnectionPoolSize' parameters."))) {
subscribeFuture.whenComplete((res, ex) -> {
if (ex == null) {
unsubscribe(res);
}
});
}
acquireFailed(waitTime, unit, threadId);
return false;
} catch (ExecutionException e) {
LOGGER.error(e.getMessage(), e);
acquireFailed(waitTime, unit, threadId);
return false;
}
try {
time -= System.currentTimeMillis() - current;
if (time <= 0) {
acquireFailed(waitTime, unit, 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(waitTime, unit, threadId);
return false;
}
// waiting for message
currentTime = System.currentTimeMillis();
if (ttl >= 0 && ttl < time) {
commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} else {
commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
}
time -= System.currentTimeMillis() - currentTime;
if (time <= 0) {
acquireFailed(waitTime, unit, threadId);
return false;
}
}
} finally {
unsubscribe(commandExecutor.getNow(subscribeFuture));
}
}
private String hashValue(Object key) {
ByteBuf objectState = encode(key);
try {
return Hash.hash128toBase64(objectState);
} finally {
objectState.release();
}
}
@Override
public void lock(long leaseTime, TimeUnit unit) {
try {
lock(leaseTime, unit, false);
} catch (InterruptedException e) {
throw new IllegalStateException();
}
}
private void lock(long leaseTime, TimeUnit unit, boolean interruptible) throws InterruptedException {
long threadId = Thread.currentThread().getId();
Long ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {
return;
}
//failed first time
CompletableFuture future = subscribe();
pubSub.timeout(future);
RedissonLockEntry entry;
if (interruptible) {
entry = commandExecutor.getInterrupted(future);
} else {
entry = commandExecutor.get(future);
}
//future.cancel(true);
try {
while (true) {
ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {
break;
}
// waiting for message
if (ttl >= 0) {
try {
entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
if (interruptible) {
throw e;
}
entry.getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
}
} else {
if (interruptible) {
entry.getLatch().acquire();
} else {
entry.getLatch().acquireUninterruptibly();
}
}
}
} finally {
unsubscribe(entry);
}
}
protected void unsubscribe(RedissonLockEntry entry) {
pubSub.unsubscribe(entry, getEntryName(), getChannelName());
}
protected CompletableFuture subscribe() {
return pubSub.subscribe(getEntryName(), getChannelName());
}
String getChannelName() {
return prefixName("redisson_lock__channel", getRawName());
}
private RFuture tryAcquireAsync(long leaseTime, TimeUnit unit, long threadId) {
RFuture ttlRemainingFuture;
if (leaseTime > 0) {
ttlRemainingFuture = tryLockInnerAsync(leaseTime, unit, threadId);
} else {
ttlRemainingFuture = tryLockInnerAsync(internalLockLeaseTime, TimeUnit.MILLISECONDS, threadId);
}
CompletionStage s = handleNoSync(threadId, ttlRemainingFuture);
ttlRemainingFuture = new CompletableFutureWrapper<>(s);
CompletionStage f = ttlRemainingFuture.thenApply(ttlRemaining -> {
// lock acquired
if (ttlRemaining == null) {
if (leaseTime > 0) {
internalLockLeaseTime = unit.toMillis(leaseTime);
} else {
scheduleExpirationRenewal(threadId);
}
}
return ttlRemaining;
});
return new CompletableFutureWrapper<>(f);
}
private Long tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
return get(tryAcquireAsync0(leaseTime, unit, threadId));
}
private RFuture tryAcquireAsync0(long leaseTime, TimeUnit unit, long threadId) {
return getServiceManager().execute(() -> tryAcquireAsync(leaseTime, unit, threadId));
}
private RFuture tryAcquireOnceAsync(long leaseTime, TimeUnit unit, long threadId) {
CompletionStage acquiredFuture;
if (leaseTime > 0) {
acquiredFuture = tryLockOnceInnerAsync(leaseTime, unit, RedisCommands.EVAL_BOOLEAN, threadId);
} else {
acquiredFuture = tryLockOnceInnerAsync(internalLockLeaseTime,
TimeUnit.MILLISECONDS, RedisCommands.EVAL_BOOLEAN, threadId);
}
acquiredFuture = handleNoSync(threadId, acquiredFuture);
CompletionStage f = acquiredFuture.thenApply(acquired -> {
// lock acquired
if (acquired) {
if (leaseTime > 0) {
internalLockLeaseTime = unit.toMillis(leaseTime);
} else {
scheduleExpirationRenewal(threadId);
}
}
return acquired;
});
return new CompletableFutureWrapper<>(f);
}
@Override
protected CompletionStage renewExpirationAsync(long threadId) {
List params = new ArrayList<>();
params.add(getLockName(threadId));
params.add(internalLockLeaseTime);
params.add(System.currentTimeMillis());
params.addAll(fields);
return commandExecutor.syncedEval(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"local leaseTime = tonumber(ARGV[2]);" +
"local currentTime = tonumber(ARGV[3]);" +
"local currentThread = ARGV[1];" +
"if (redis.call('exists',KEYS[1]) > 0) then" +
" local newExpireTime = leaseTime + currentTime;" +
" for i=4, #ARGV, 1 do " +
" local lockThread = redis.call('hget', KEYS[1], ARGV[i]);" +
" if(lockThread ~= false and lockThread == currentThread) then " +
" local expireFieldName = ARGV[i]..':'..lockThread..':expire_time';" +
" local expireTime = redis.call('hget', KEYS[1], expireFieldName);" +
" if(tonumber(expireTime) < newExpireTime) then " +
" redis.call('hset', KEYS[1],expireFieldName, newExpireTime);" +
" end;" +
" else" +
" return 0;" +
" end;" +
" end; " +
" local expireTime = redis.call('pttl',KEYS[1]);" +
" if(tonumber(expireTime) < tonumber(leaseTime)) then " +
" redis.call('pexpire',KEYS[1], leaseTime);" +
" end;" +
" return 1;" +
"end;" +
"return 0;",
Collections.singletonList(getRawName()),
params.toArray());
}
private RFuture tryLockOnceInnerAsync(long leaseTime, TimeUnit unit, RedisStrictCommand command, long threadId) {
List params = new ArrayList<>();
params.add(String.valueOf(System.currentTimeMillis()));
params.add(String.valueOf(unit.toMillis(leaseTime)));
params.add(getLockName(threadId));
params.addAll(fields);
return commandExecutor.syncedEval(key, StringCodec.INSTANCE, command,
"local currentTime = tonumber(ARGV[1]);" +
"local leaseTime = tonumber(ARGV[2]);" +
"local currentThread = ARGV[3];" +
"local keyExist = nil;" +
"if (redis.call('exists',KEYS[1]) > 0) then" +
" keyExist = 1;" +
" for i=4, #ARGV, 1 do " +
" local lockThread = redis.call('hget', KEYS[1], ARGV[i]); " +
" if(lockThread ~= false and currentThread ~= lockThread ) then " +
" local expireFieldName = ARGV[i]..':'..lockThread..':expire_time'" +
" local expireTime = redis.call('hget', KEYS[1], expireFieldName);" +
" if( tonumber(expireTime) > currentTime and currentThread ~= lockThread) then " +
" return 0;" +
" end" +
" end; " +
" end; " +
"else" +
" keyExist = 0;" +
"end;" +
"for i=4, #ARGV, 1 do " +
" redis.call('hset', KEYS[1], ARGV[i], currentThread); " +
" local expireFieldName = ARGV[i]..':'..currentThread..':expire_time'" +
" local expireTime = redis.call('hget', KEYS[1], expireFieldName); " +
" local newExpireTime = leaseTime + currentTime " +
" if(expireTime == false or tonumber(expireTime) < newExpireTime ) then" +
" redis.call('hset', KEYS[1], expireFieldName, newExpireTime); " +
" end;" +
" redis.call('HINCRBY', KEYS[1], ARGV[i]..':'..currentThread..':lock_count', 1); " +
"end; " +
"if(keyExist == 1) then" +
" local expireTime = redis.call('pttl',KEYS[1]);" +
" if(tonumber(expireTime) > 0 and leaseTime > tonumber(expireTime)) then" +
" redis.call('pexpire',KEYS[1], leaseTime);" +
" end;"+
"else" +
" redis.call('pexpire',KEYS[1], leaseTime);" +
"end;" +
"return 1;",
Collections.singletonList(getRawName()), params.toArray());
}
private RFuture tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId) {
List params = new ArrayList<>();
params.add(String.valueOf(unit.toMillis(leaseTime)));
params.add(String.valueOf(System.currentTimeMillis()));
params.add(getLockName(threadId));
params.addAll(fields);
return commandExecutor.syncedEval(key, StringCodec.INSTANCE, RedisCommands.EVAL_LONG,
"local leaseTime = tonumber(ARGV[1]);" +
"local currentTime = tonumber(ARGV[2]);" +
"local currentThread = ARGV[3];" +
"local maxExpireTime = -1;" +
"local keyExist = nil;" +
"if (tonumber(redis.call('exists',KEYS[1])) > 0) then" +
" keyExist = 1;" +
" for i=4, #ARGV, 1 do " +
" local lockThread = redis.call('hget', KEYS[1], ARGV[i]); " +
" if(lockThread ~= false and lockThread ~= currentThread) then " +
" local expireFieldName = ARGV[i]..':'..lockThread..':expire_time';" +
" local expireTime = redis.call('hget', KEYS[1], expireFieldName);" +
" if(expireTime ~= false and tonumber(expireTime) > currentTime ) then " +
" if(tonumber(expireTime) > maxExpireTime) then" +
" maxExpireTime = tonumber(expireTime);" +
" end;" +
" end" +
" end; " +
" end; " +
"else" +
" keyExist = 0;" +
"end;" +
"if( maxExpireTime ~= -1) then" +
" return maxExpireTime-currentTime;" +
"end;" +
"for i=4, #ARGV, 1 do " +
" redis.call('hset', KEYS[1], ARGV[i], currentThread); " +
" local expireFieldName = ARGV[i]..':'..currentThread..':expire_time'" +
" local expireTime = redis.call('hget', KEYS[1], expireFieldName); " +
" local newExpireTime = leaseTime + currentTime " +
" if(expireTime == false or tonumber(expireTime) < newExpireTime ) then" +
" redis.call('hset', KEYS[1], expireFieldName, newExpireTime); " +
" end;" +
" redis.call('HINCRBY', KEYS[1], ARGV[i]..':'..currentThread..':lock_count', 1); " +
"end; " +
"if(keyExist == 1) then" +
" local expireTime = redis.call('pttl',KEYS[1]);" +
" if(tonumber(expireTime) > 0 and leaseTime > tonumber(expireTime)) then" +
" redis.call('pexpire',KEYS[1], leaseTime);" +
" end;"+
"else" +
" redis.call('pexpire',KEYS[1], leaseTime);" +
"end;" +
"return nil;",
Collections.singletonList(getRawName()), params.toArray());
}
@Override
public boolean forceUnlock() {
return get(forceUnlockAsync());
}
@Override
public boolean isLocked() {
return get(isLockedAsync());
}
@Override
public boolean isHeldByThread(long threadId) {
return get(isHeldByThreadAsync(threadId));
}
@Override
public boolean isHeldByCurrentThread() {
return isHeldByThread(Thread.currentThread().getId());
}
@Override
public int getHoldCount() {
throw new UnsupportedOperationException();
}
@Override
public long remainTimeToLive() {
throw new UnsupportedOperationException();
}
@Override
public void lock() {
try {
lock(-1, null, false);
} catch (InterruptedException e) {
throw new IllegalStateException();
}
}
@Override
protected RFuture unlockInnerAsync(long threadId, String requestId, int timeout) {
List params = new ArrayList<>();
params.add(getLockName(threadId));
params.add(LockPubSub.UNLOCK_MESSAGE);
params.add(internalLockLeaseTime);
params.add(getSubscribeService().getPublishCommand());
params.add(timeout);
params.add(System.currentTimeMillis());
params.addAll(fields);
return commandExecutor.syncedEval(getRawName(), StringCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"local val = redis.call('get', KEYS[3]); " +
"if val ~= false then " +
" return tonumber(val);" +
"end; " +
"if(redis.call('exists', KEYS[1]) == 0) then" +
" redis.call(ARGV[4], KEYS[2], ARGV[2]); " +
" return nil;" +
"end;" +
"local lockName = ARGV[1];" +
"local hasFailed = 0;" +
"local allDeleted = 1;"+
"local hasDeleted = 0;" +
"local newExpireTime = tonumber(ARGV[3]) + tonumber(ARGV[6]);"+
"for i = 7,#ARGV,1 do" +
" local expireTime = redis.call('hget', KEYS[1], ARGV[i]..':'..lockName..':expire_time');" +
" if (expireTime ~= false and tonumber(expireTime) >= tonumber(ARGV[6])) then" +
" if(tonumber(redis.call('hincrby', KEYS[1], ARGV[i]..':'..lockName..':lock_count',-1)) <= 0) then" +
" redis.call('hdel',KEYS[1], ARGV[i]..':'..lockName..':expire_time');" +
" redis.call('hdel',KEYS[1], ARGV[i]..':'..lockName..':lock_count');" +
" redis.call('hdel',KEYS[1], ARGV[i]);" +
" hasDeleted = 1;" +
" else" +
" if(tonumber(expireTime) < newExpireTime) then " +
" redis.call('hset', KEYS[1], ARGV[i]..':'..lockName..':expire_time', newExpireTime);" +
" end;" +
" allDeleted = 0;" +
" end;" +
" else" +
" allDeleted = 0;" +
" hasFailed = 1;" +
" end;" +
"end;" +
"if(hasFailed ~= 0) then" +
" return nil;" +
"end;" +
"if(hasDeleted) then" +
" redis.call(ARGV[4], KEYS[2], ARGV[2]); " +
"end;"+
"if(allDeleted == 1) then" +
" redis.call('set', KEYS[3], 1, 'px', ARGV[5]); " +
"else " +
" redis.call('set', KEYS[3], 0, 'px', ARGV[5]); " +
" local expireTime = redis.call('pttl',KEYS[1]);" +
" if(tonumber(ARGV[3]) > tonumber(expireTime)) then" +
" redis.call('pexpire',KEYS[1], ARGV[3]);" +
" end;"+
"end;"+
"return allDeleted;",
Arrays.asList(getRawName(), getChannelName(), getUnlockLatchName(requestId)),
params.toArray());
}
@Override
public void lockInterruptibly() throws InterruptedException {
lock(-1, null, false);
}
@Override
public boolean tryLock() {
return get(tryLockAsync());
}
@Override
public boolean tryLock(long waitTime, TimeUnit unit) throws InterruptedException {
return tryLock(waitTime, -1, unit);
}
@Override
public RFuture forceUnlockAsync() {
cancelExpirationRenewal(null, null);
List params = new ArrayList<>();
params.add(LockPubSub.UNLOCK_MESSAGE);
params.add(getSubscribeService().getPublishCommand());
params.addAll(fields);
return commandExecutor.syncedEvalWithRetry(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"local removeCount = 0;" +
"for i=3, #ARGV, 1 do " +
" local lockName = redis.call('hget', KEYS[1], ARGV[i]); " +
" local count = redis.call('HDEL', KEYS[1], ARGV[i]); " +
" redis.call('HDEL', KEYS[1], ARGV[i]..':'..lockName..':expire_time'); " +
" redis.call('HDEL', KEYS[1], ARGV[i]..':'..lockName..':lock_count'); " +
" removeCount = removeCount+count;" +
"end; " +
"redis.call(ARGV[2], KEYS[2], ARGV[1]); "+
"return removeCount; ",
Arrays.asList(getRawName(), getChannelName()),
params.toArray());
}
@Override
public RFuture unlockAsync() {
return unlockAsync(Thread.currentThread().getId());
}
@Override
public RFuture unlockAsync(long threadId) {
return getServiceManager().execute(() -> unlockAsync0(threadId));
}
private RFuture unlockAsync0(long threadId) {
CompletionStage future = unlockInnerAsync(threadId);
CompletionStage f = future.handle((res, e) -> {
cancelExpirationRenewal(threadId, res);
if (e != null) {
if (e instanceof CompletionException) {
throw (CompletionException) e;
}
throw new CompletionException(e);
}
if (res == null) {
IllegalMonitorStateException cause = new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: "
+ id + " thread-id: " + threadId);
throw new CompletionException(cause);
}
return null;
});
return new CompletableFutureWrapper<>(f);
}
@Override
public RFuture lockAsync(long leaseTime, TimeUnit unit, long currentThreadId) {
CompletableFuture result = new CompletableFuture<>();
RFuture ttlFuture = tryLockInnerAsync(leaseTime, unit, currentThreadId);
ttlFuture.whenComplete((ttl, e) -> {
if (e != null) {
result.completeExceptionally(e);
return;
}
// lock acquired
if (ttl == null) {
if (!result.complete(null)) {
unlockAsync(currentThreadId);
}
return;
}
CompletableFuture subscribeFuture = subscribe();
pubSub.timeout(subscribeFuture);
subscribeFuture.whenComplete((res, ex) -> {
if (ex != null) {
result.completeExceptionally(ex);
return;
}
lockAsync(leaseTime, unit, res, result, currentThreadId);
});
});
return new CompletableFutureWrapper<>(result);
}
@Override
public RFuture tryLockAsync() {
return getServiceManager().execute(() -> tryAcquireOnceAsync(-1, null, Thread.currentThread().getId()));
}
@Override
public RFuture tryLockAsync(long threadId) {
return getServiceManager().execute(() -> tryAcquireOnceAsync(-1, null, threadId));
}
@Override
public RFuture tryLockAsync(long waitTime, TimeUnit unit) {
return getServiceManager().execute(() -> tryLockAsync(waitTime, -1, unit, Thread.currentThread().getId()));
}
@Override
public RFuture tryLockAsync(long waitTime, long leaseTime, TimeUnit unit) {
return getServiceManager().execute(() -> tryLockAsync(waitTime, leaseTime, unit, Thread.currentThread().getId()));
}
@Override
public RFuture tryLockAsync(long waitTime, long leaseTime, TimeUnit unit, long currentThreadId) {
CompletableFuture result = new CompletableFuture<>();
AtomicLong time = new AtomicLong(unit.toMillis(waitTime));
long currentTime = System.currentTimeMillis();
RFuture ttlFuture = tryAcquireAsync0(leaseTime, unit, currentThreadId);
ttlFuture.whenComplete((ttl, e) -> {
if (e != null) {
result.completeExceptionally(e);
return;
}
// lock acquired
if (ttl == null) {
if (!result.complete(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<>();
CompletableFuture subscribeFuture = subscribe();
pubSub.timeout(subscribeFuture, time.get());
subscribeFuture.whenComplete((r, ex) -> {
if (ex != null) {
result.completeExceptionally(ex);
return;
}
if (futureRef.get() != null) {
futureRef.get().cancel();
}
long elapsed = System.currentTimeMillis() - current;
time.addAndGet(-elapsed);
tryLockAsync(time, leaseTime, unit, r, result, currentThreadId);
});
if (!subscribeFuture.isDone()) {
Timeout scheduledFuture = getServiceManager().newTimeout(timeout -> {
if (!subscribeFuture.isDone()) {
subscribeFuture.cancel(false);
trySuccessFalse(currentThreadId, result);
}
}, time.get(), TimeUnit.MILLISECONDS);
futureRef.set(scheduledFuture);
}
});
return new CompletableFutureWrapper<>(result);
}
@Override
public RFuture isHeldByThreadAsync(long threadId) {
List params = new ArrayList<>();
params.add(String.valueOf(System.currentTimeMillis()));
params.add(getLockName(threadId));
params.addAll(fields);
return commandExecutor.syncedEval(getRawName(), StringCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"local currentTime = tonumber(ARGV[1]);" +
"local currentThread = ARGV[2];" +
"if (redis.call('exists',KEYS[1]) > 0) then" +
" for i=4, #ARGV, 1 do " +
" local expireTime = redis.call('hget', KEYS[1], ARGV[i]..':'..currentThread..':expire_time'); " +
" if(expireTime == false or tonumber(expireTime) < currentTime) then " +
" return 0;" +
" end;" +
" end; " +
"else" +
" return 0;" +
"end;" +
"return 1;",
Collections.singletonList(getRawName()), params.toArray());
}
@Override
public RFuture getHoldCountAsync() {
throw new UnsupportedOperationException();
}
/**
*
* @return any one locked,return true,else false
*/
@Override
public RFuture isLockedAsync() {
List params = new ArrayList<>();
params.add(String.valueOf(System.currentTimeMillis()));
params.addAll(fields);
return commandExecutor.syncedEval(getRawName(), StringCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"local currentTime = tonumber(ARGV[1]);" +
"for i = 2,#ARGV,1 do" +
" local localThread = redis.call('hget', KEYS[1], ARGV[i]);" +
" if(localThread ~= false) then" +
" local expireTime = redis.call('hget', KEYS[1], ARGV[i]..':'..localThread..':expire_time');" +
" if (expireTime ~= false and tonumber(expireTime) >= currentTime) then" +
" return 1;" +
" end;" +
" end;" +
"end;" +
"return 0;",
Collections.singletonList(getRawName()), params.toArray());
}
@Override
public RFuture remainTimeToLiveAsync() {
throw new UnsupportedOperationException();
}
private void tryLockAsync(AtomicLong time, long leaseTime, TimeUnit unit,
RedissonLockEntry entry, CompletableFuture result, long currentThreadId) {
if (result.isDone()) {
unsubscribe(entry);
return;
}
if (time.get() <= 0) {
unsubscribe(entry);
trySuccessFalse(currentThreadId, result);
return;
}
long curr = System.currentTimeMillis();
RFuture ttlFuture = tryLockInnerAsync(leaseTime, unit, currentThreadId);
ttlFuture.whenComplete((ttl, e) -> {
if (e != null) {
unsubscribe(entry);
result.completeExceptionally(e);
return;
}
// lock acquired
if (ttl == null) {
unsubscribe(entry);
if (!result.complete(true)) {
unlockAsync(currentThreadId);
}
return;
}
long el = System.currentTimeMillis() - curr;
time.addAndGet(-el);
if (time.get() <= 0) {
unsubscribe(entry);
trySuccessFalse(currentThreadId, result);
return;
}
// waiting for message
long current = System.currentTimeMillis();
if (entry.getLatch().tryAcquire()) {
tryLockAsync(time, leaseTime, unit, entry, 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, entry, result, currentThreadId);
};
entry.addListener(listener);
long t = time.get();
if (ttl >= 0 && ttl < time.get()) {
t = ttl;
}
if (!executed.get()) {
Timeout scheduledFuture = getServiceManager().newTimeout(timeout -> {
if (entry.removeListener(listener)) {
long elapsed = System.currentTimeMillis() - current;
time.addAndGet(-elapsed);
tryLockAsync(time, leaseTime, unit, entry, result, currentThreadId);
}
}, t, TimeUnit.MILLISECONDS);
futureRef.set(scheduledFuture);
}
}
});
}
private void lockAsync(long leaseTime, TimeUnit unit,
RedissonLockEntry entry, CompletableFuture result, long currentThreadId) {
RFuture ttlFuture = tryLockInnerAsync(leaseTime, unit, currentThreadId);
ttlFuture.whenComplete((ttl, e) -> {
if (e != null) {
unsubscribe(entry);
result.completeExceptionally(e);
return;
}
// lock acquired
if (ttl == null) {
unsubscribe(entry);
if (!result.complete(null)) {
unlockAsync(currentThreadId);
}
return;
}
if (entry.getLatch().tryAcquire()) {
lockAsync(leaseTime, unit, entry, result, currentThreadId);
} else {
// waiting for message
AtomicReference futureRef = new AtomicReference<>();
Runnable listener = () -> {
if (futureRef.get() != null) {
futureRef.get().cancel();
}
lockAsync(leaseTime, unit, entry, result, currentThreadId);
};
entry.addListener(listener);
if (ttl >= 0) {
Timeout scheduledFuture = getServiceManager().newTimeout(timeout -> {
if (entry.removeListener(listener)) {
lockAsync(leaseTime, unit, entry, result, currentThreadId);
}
}, ttl, TimeUnit.MILLISECONDS);
futureRef.set(scheduledFuture);
}
}
});
}
}