org.redisson.RedissonPermitExpirableSemaphore 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-2021 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.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.redisson.api.RFuture;
import org.redisson.api.RPermitExpirableSemaphore;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.misc.RPromise;
import org.redisson.misc.RedissonPromise;
import org.redisson.pubsub.SemaphorePubSub;
import io.netty.buffer.ByteBufUtil;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
/**
*
* @author Nikita Koksharov
*
*/
public class RedissonPermitExpirableSemaphore extends RedissonExpirable implements RPermitExpirableSemaphore {
private final SemaphorePubSub semaphorePubSub;
final CommandAsyncExecutor commandExecutor;
private final String timeoutName;
private final long nonExpirableTimeout = 922337203685477L;
public RedissonPermitExpirableSemaphore(CommandAsyncExecutor commandExecutor, String name) {
super(commandExecutor, name);
this.timeoutName = suffixName(name, "timeout");
this.commandExecutor = commandExecutor;
this.semaphorePubSub = commandExecutor.getConnectionManager().getSubscribeService().getSemaphorePubSub();
}
String getChannelName() {
return getChannelName(getRawName());
}
public static String getChannelName(String name) {
if (name.contains("{")) {
return "redisson_sc:" + name;
}
return "redisson_sc:{" + name + "}";
}
@Override
public String acquire() throws InterruptedException {
return acquire(1, -1, TimeUnit.MILLISECONDS);
}
@Override
public String acquire(long leaseTime, TimeUnit timeUnit) throws InterruptedException {
return acquire(1, leaseTime, timeUnit);
}
@Override
public RFuture acquireAsync(long leaseTime, TimeUnit timeUnit) {
return acquireAsync(1, leaseTime, timeUnit);
}
private String acquire(int permits, long ttl, TimeUnit timeUnit) throws InterruptedException {
String permitId = tryAcquire(permits, ttl, timeUnit);
if (permitId != null && !permitId.startsWith(":")) {
return permitId;
}
RFuture future = subscribe();
commandExecutor.syncSubscriptionInterrupted(future);
try {
while (true) {
Long nearestTimeout;
permitId = tryAcquire(permits, ttl, timeUnit);
if (permitId != null) {
if (!permitId.startsWith(":")) {
return permitId;
} else {
nearestTimeout = Long.valueOf(permitId.substring(1)) - System.currentTimeMillis();
}
} else {
nearestTimeout = null;
}
if (nearestTimeout != null) {
future.getNow().getLatch().tryAcquire(permits, nearestTimeout, TimeUnit.MILLISECONDS);
} else {
future.getNow().getLatch().acquire(permits);
}
}
} finally {
unsubscribe(future);
}
// return get(acquireAsync(permits, ttl, timeUnit));
}
public RFuture acquireAsync() {
return acquireAsync(1, -1, TimeUnit.MILLISECONDS);
}
private RFuture acquireAsync(int permits, long ttl, TimeUnit timeUnit) {
RPromise result = new RedissonPromise();
long timeoutDate = calcTimeout(ttl, timeUnit);
RFuture tryAcquireFuture = tryAcquireAsync(permits, timeoutDate);
tryAcquireFuture.onComplete((permitId, e) -> {
if (e != null) {
result.tryFailure(e);
return;
}
if (permitId != null && !permitId.startsWith(":")) {
if (!result.trySuccess(permitId)) {
releaseAsync(permitId);
}
return;
}
RFuture subscribeFuture = subscribe();
subscribeFuture.onComplete((res, ex) -> {
if (ex != null) {
result.tryFailure(ex);
return;
}
acquireAsync(permits, subscribeFuture, result, ttl, timeUnit);
});
});
return result;
}
private void tryAcquireAsync(AtomicLong time, int permits, RFuture subscribeFuture, RPromise result, long ttl, TimeUnit timeUnit) {
if (result.isDone()) {
unsubscribe(subscribeFuture);
return;
}
if (time.get() <= 0) {
unsubscribe(subscribeFuture);
result.trySuccess(null);
return;
}
long timeoutDate = calcTimeout(ttl, timeUnit);
long curr = System.currentTimeMillis();
RFuture tryAcquireFuture = tryAcquireAsync(permits, timeoutDate);
tryAcquireFuture.onComplete((permitId, e) -> {
if (e != null) {
unsubscribe(subscribeFuture);
result.tryFailure(e);
return;
}
Long nearestTimeout;
if (permitId != null) {
if (!permitId.startsWith(":")) {
unsubscribe(subscribeFuture);
if (!result.trySuccess(permitId)) {
releaseAsync(permitId);
}
return;
} else {
nearestTimeout = Long.valueOf(permitId.substring(1)) - System.currentTimeMillis();
}
} else {
nearestTimeout = null;
}
long el = System.currentTimeMillis() - curr;
time.addAndGet(-el);
if (time.get() <= 0) {
unsubscribe(subscribeFuture);
result.trySuccess(null);
return;
}
// waiting for message
long current = System.currentTimeMillis();
RedissonLockEntry entry = subscribeFuture.getNow();
if (entry.getLatch().tryAcquire()) {
tryAcquireAsync(time, permits, subscribeFuture, result, ttl, timeUnit);
} else {
AtomicReference waitTimeoutFutureRef = new AtomicReference();
Timeout scheduledFuture;
if (nearestTimeout != null) {
scheduledFuture = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
if (waitTimeoutFutureRef.get() != null && !waitTimeoutFutureRef.get().cancel()) {
return;
}
long elapsed = System.currentTimeMillis() - current;
time.addAndGet(-elapsed);
tryAcquireAsync(time, permits, subscribeFuture, result, ttl, timeUnit);
}
}, nearestTimeout, TimeUnit.MILLISECONDS);
} else {
scheduledFuture = null;
}
Runnable listener = () -> {
if (waitTimeoutFutureRef.get() != null && !waitTimeoutFutureRef.get().cancel()) {
entry.getLatch().release();
return;
}
if (scheduledFuture != null && !scheduledFuture.cancel()) {
entry.getLatch().release();
return;
}
long elapsed = System.currentTimeMillis() - current;
time.addAndGet(-elapsed);
tryAcquireAsync(time, permits, subscribeFuture, result, ttl, timeUnit);
};
entry.addListener(listener);
long t = time.get();
Timeout waitTimeoutFuture = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
if (scheduledFuture != null && !scheduledFuture.cancel()) {
return;
}
if (entry.removeListener(listener)) {
long elapsed = System.currentTimeMillis() - current;
time.addAndGet(-elapsed);
tryAcquireAsync(time, permits, subscribeFuture, result, ttl, timeUnit);
}
}
}, t, TimeUnit.MILLISECONDS);
waitTimeoutFutureRef.set(waitTimeoutFuture);
}
});
}
private void acquireAsync(int permits, RFuture subscribeFuture, RPromise result, long ttl, TimeUnit timeUnit) {
if (result.isDone()) {
unsubscribe(subscribeFuture);
return;
}
long timeoutDate = calcTimeout(ttl, timeUnit);
RFuture tryAcquireFuture = tryAcquireAsync(permits, timeoutDate);
tryAcquireFuture.onComplete((permitId, e) -> {
if (e != null) {
unsubscribe(subscribeFuture);
result.tryFailure(e);
return;
}
Long nearestTimeout;
if (permitId != null) {
if (!permitId.startsWith(":")) {
unsubscribe(subscribeFuture);
if (!result.trySuccess(permitId)) {
releaseAsync(permitId);
}
return;
} else {
nearestTimeout = Long.valueOf(permitId.substring(1)) - System.currentTimeMillis();
}
} else {
nearestTimeout = null;
}
RedissonLockEntry entry = subscribeFuture.getNow();
if (entry.getLatch().tryAcquire(permits)) {
acquireAsync(permits, subscribeFuture, result, ttl, timeUnit);
} else {
Timeout scheduledFuture;
if (nearestTimeout != null) {
scheduledFuture = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
acquireAsync(permits, subscribeFuture, result, ttl, timeUnit);
}
}, nearestTimeout, TimeUnit.MILLISECONDS);
} else {
scheduledFuture = null;
}
Runnable listener = () -> {
if (scheduledFuture != null && !scheduledFuture.cancel()) {
entry.getLatch().release();
return;
}
acquireAsync(permits, subscribeFuture, result, ttl, timeUnit);
};
entry.addListener(listener);
}
});
}
@Override
public String tryAcquire() {
String res = tryAcquire(1, -1, TimeUnit.MILLISECONDS);
if (res != null && res.startsWith(":")) {
return null;
}
return res;
}
private String tryAcquire(int permits, long ttl, TimeUnit timeUnit) {
long timeoutDate = calcTimeout(ttl, timeUnit);
return get(tryAcquireAsync(permits, timeoutDate));
}
private long calcTimeout(long ttl, TimeUnit timeUnit) {
if (ttl != -1) {
return System.currentTimeMillis() + timeUnit.toMillis(ttl);
}
return nonExpirableTimeout;
}
public RFuture tryAcquireAsync() {
RPromise result = new RedissonPromise();
RFuture future = tryAcquireAsync(1, nonExpirableTimeout);
future.onComplete((permitId, e) -> {
if (e != null) {
result.tryFailure(e);
return;
}
if (permitId != null && !permitId.startsWith(":")) {
if (!result.trySuccess(permitId)) {
releaseAsync(permitId);
}
} else {
result.trySuccess(null);
}
});
return result;
}
protected String generateId() {
byte[] id = new byte[16];
ThreadLocalRandom.current().nextBytes(id);
return ByteBufUtil.hexDump(id);
}
public RFuture tryAcquireAsync(int permits, long timeoutDate) {
if (permits < 0) {
throw new IllegalArgumentException("Permits amount can't be negative");
}
String id = generateId();
return commandExecutor.evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_STRING_DATA,
"local expiredIds = redis.call('zrangebyscore', KEYS[2], 0, ARGV[4], 'limit', 0, ARGV[1]); " +
"if #expiredIds > 0 then " +
"redis.call('zrem', KEYS[2], unpack(expiredIds)); " +
"local value = redis.call('incrby', KEYS[1], #expiredIds); " +
"if tonumber(value) > 0 then " +
"redis.call('publish', KEYS[3], value); " +
"end;" +
"end; " +
"local value = redis.call('get', KEYS[1]); " +
"if (value ~= false and tonumber(value) >= tonumber(ARGV[1])) then " +
"redis.call('decrby', KEYS[1], ARGV[1]); " +
"redis.call('zadd', KEYS[2], ARGV[2], ARGV[3]); " +
"local ttl = redis.call('pttl', KEYS[1]); " +
"if ttl > 0 then " +
"redis.call('pexpire', KEYS[2], ttl); " +
"end; " +
"return ARGV[3]; " +
"end; " +
"local v = redis.call('zrange', KEYS[2], 0, 0, 'WITHSCORES'); " +
"if v[1] ~= nil and v[2] ~= ARGV[5] then " +
"return ':' .. tostring(v[2]); " +
"end " +
"return nil;",
Arrays.
© 2015 - 2025 Weber Informatics LLC | Privacy Policy