org.redisson.RedissonBaseLock Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of redisson-all Show documentation
Show all versions of redisson-all Show documentation
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.util.Timeout;
import io.netty.util.TimerTask;
import org.redisson.api.RFuture;
import org.redisson.api.RLock;
import org.redisson.client.RedisException;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.client.protocol.convertor.IntegerReplayConvertor;
import org.redisson.client.protocol.decoder.MapValueDecoder;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.command.CommandBatchService;
import org.redisson.config.MasterSlaveServersConfig;
import org.redisson.misc.CompletableFutureWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.Condition;
/**
* Base class for implementing distributed locks
*
* @author Danila Varatyntsev
* @author Nikita Koksharov
*/
public abstract class RedissonBaseLock extends RedissonExpirable implements RLock {
public static class ExpirationEntry {
private final Queue threadsQueue = new ConcurrentLinkedQueue<>();
private final Map threadIds = new ConcurrentHashMap<>();
private volatile Timeout timeout;
public ExpirationEntry() {
super();
}
public void addThreadId(long threadId) {
threadIds.compute(threadId, (t, counter) -> {
counter = Optional.ofNullable(counter).orElse(0);
counter++;
threadsQueue.add(threadId);
return counter;
});
}
public boolean hasNoThreads() {
return threadsQueue.isEmpty();
}
public Long getFirstThreadId() {
return threadsQueue.peek();
}
public void removeThreadId(long threadId) {
threadIds.computeIfPresent(threadId, (t, counter) -> {
counter--;
if (counter == 0) {
threadsQueue.remove(threadId);
return null;
}
return counter;
});
}
public void setTimeout(Timeout timeout) {
this.timeout = timeout;
}
public Timeout getTimeout() {
return timeout;
}
}
private static final Logger log = LoggerFactory.getLogger(RedissonBaseLock.class);
private static final ConcurrentMap EXPIRATION_RENEWAL_MAP = new ConcurrentHashMap<>();
protected long internalLockLeaseTime;
final String id;
final String entryName;
public RedissonBaseLock(CommandAsyncExecutor commandExecutor, String name) {
super(commandExecutor, name);
this.id = getServiceManager().getId();
this.internalLockLeaseTime = getServiceManager().getCfg().getLockWatchdogTimeout();
this.entryName = id + ":" + name;
}
protected String getEntryName() {
return entryName;
}
protected String getLockName(long threadId) {
return id + ":" + threadId;
}
private void renewExpiration() {
ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ee == null) {
return;
}
Timeout task = getServiceManager().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;
}
CompletionStage future = renewExpirationAsync(threadId);
future.whenComplete((res, e) -> {
if (e != null) {
if (getServiceManager().isShuttingDown(e)) {
return;
}
log.error("Can't update lock {} expiration", getRawName(), e);
EXPIRATION_RENEWAL_MAP.remove(getEntryName());
return;
}
if (res) {
// reschedule itself
renewExpiration();
} else {
cancelExpirationRenewal(null, null);
}
});
}
}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
ee.setTimeout(task);
}
protected void scheduleExpirationRenewal(long threadId) {
ExpirationEntry entry = new ExpirationEntry();
entry.addThreadId(threadId);
ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);
if (oldEntry != null) {
oldEntry.addThreadId(threadId);
} else {
try {
renewExpiration();
} finally {
if (Thread.currentThread().isInterrupted()) {
cancelExpirationRenewal(threadId, null);
}
}
}
}
protected CompletionStage renewExpirationAsync(long threadId) {
return commandExecutor.syncedEval(getRawName(), 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(getRawName()),
internalLockLeaseTime, getLockName(threadId));
}
protected void cancelExpirationRenewal(Long threadId, Boolean unlockResult) {
ExpirationEntry task = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (task == null) {
return;
}
if (threadId != null) {
task.removeThreadId(threadId);
}
if (threadId == null || task.hasNoThreads()) {
Timeout timeout = task.getTimeout();
if (timeout != null) {
timeout.cancel();
}
EXPIRATION_RENEWAL_MAP.remove(getEntryName());
}
}
protected final RFuture evalWriteSyncedNoRetryAsync(String key, Codec codec, RedisCommand evalCommandType, String script, List
© 2015 - 2024 Weber Informatics LLC | Privacy Policy