org.redisson.RedissonSemaphore Maven / Gradle / Ivy
Show all versions of redisson-all Show documentation
/**
* 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.RSemaphore;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.misc.CompletableFutureWrapper;
import org.redisson.pubsub.SemaphorePubSub;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
/**
* Distributed and concurrent implementation of {@link java.util.concurrent.Semaphore}.
*
* Works in non-fair mode. Therefore order of acquiring is unpredictable.
*
* @author Nikita Koksharov
*
*/
public class RedissonSemaphore extends RedissonExpirable implements RSemaphore {
private static final Logger LOGGER = LoggerFactory.getLogger(RedissonSemaphore.class);
private final SemaphorePubSub semaphorePubSub;
public RedissonSemaphore(CommandAsyncExecutor commandExecutor, String name) {
super(commandExecutor, name);
this.semaphorePubSub = getSubscribeService().getSemaphorePubSub();
}
String getChannelName() {
return getChannelName(getRawName());
}
public static String getChannelName(String name) {
return prefixName("redisson_sc", name);
}
@Override
public void acquire() throws InterruptedException {
acquire(1);
}
@Override
public void acquire(int permits) throws InterruptedException {
if (tryAcquire(permits)) {
return;
}
CompletableFuture future = subscribe();
semaphorePubSub.timeout(future);
RedissonLockEntry entry = commandExecutor.getInterrupted(future);
try {
while (true) {
if (tryAcquire(permits)) {
return;
}
entry.getLatch().acquire();
}
} finally {
unsubscribe(entry);
}
// get(acquireAsync(permits));
}
@Override
public RFuture acquireAsync() {
return acquireAsync(1);
}
@Override
public RFuture acquireAsync(int permits) {
CompletableFuture result = new CompletableFuture<>();
RFuture tryAcquireFuture = tryAcquireAsync(permits);
tryAcquireFuture.whenComplete((res, e) -> {
if (e != null) {
result.completeExceptionally(e);
return;
}
if (res) {
if (!result.complete(null)) {
releaseAsync(permits);
}
return;
}
CompletableFuture subscribeFuture = subscribe();
semaphorePubSub.timeout(subscribeFuture);
subscribeFuture.whenComplete((r, e1) -> {
if (e1 != null) {
result.completeExceptionally(e1);
return;
}
acquireAsync(permits, r, result);
});
});
return new CompletableFutureWrapper<>(result);
}
private void tryAcquireAsync(AtomicLong time, int permits, RedissonLockEntry entry, CompletableFuture result) {
if (result.isDone()) {
unsubscribe(entry);
return;
}
if (time.get() <= 0) {
unsubscribe(entry);
result.complete(false);
return;
}
long curr = System.currentTimeMillis();
RFuture tryAcquireFuture = tryAcquireAsync(permits);
tryAcquireFuture.whenComplete((res, e) -> {
if (e != null) {
unsubscribe(entry);
result.completeExceptionally(e);
return;
}
if (res) {
unsubscribe(entry);
if (!result.complete(true)) {
releaseAsync(permits);
}
return;
}
long el = System.currentTimeMillis() - curr;
time.addAndGet(-el);
if (time.get() <= 0) {
unsubscribe(entry);
result.complete(false);
return;
}
// waiting for message
long current = System.currentTimeMillis();
if (entry.getLatch().tryAcquire()) {
tryAcquireAsync(time, permits, entry, result);
} else {
AtomicBoolean executed = new AtomicBoolean();
AtomicReference futureRef = new AtomicReference<>();
Runnable listener = () -> {
executed.set(true);
if (futureRef.get() != null && !futureRef.get().cancel()) {
entry.getLatch().release();
return;
}
long elapsed = System.currentTimeMillis() - current;
time.addAndGet(-elapsed);
tryAcquireAsync(time, permits, entry, result);
};
entry.addListener(listener);
long t = time.get();
if (!executed.get()) {
Timeout scheduledFuture = commandExecutor.getServiceManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
if (entry.removeListener(listener)) {
long elapsed = System.currentTimeMillis() - current;
time.addAndGet(-elapsed);
tryAcquireAsync(time, permits, entry, result);
}
}
}, t, TimeUnit.MILLISECONDS);
futureRef.set(scheduledFuture);
}
}
});
}
private void acquireAsync(int permits, RedissonLockEntry entry, CompletableFuture result) {
if (result.isDone()) {
unsubscribe(entry);
return;
}
RFuture tryAcquireFuture = tryAcquireAsync(permits);
tryAcquireFuture.whenComplete((res, e) -> {
if (e != null) {
unsubscribe(entry);
result.completeExceptionally(e);
return;
}
if (res) {
unsubscribe(entry);
if (!result.complete(null)) {
releaseAsync(permits);
}
return;
}
if (entry.getLatch().tryAcquire()) {
acquireAsync(permits, entry, result);
} else {
entry.addListener(() -> {
acquireAsync(permits, entry, result);
});
}
});
}
@Override
public boolean tryAcquire() {
return tryAcquire(1);
}
@Override
public boolean tryAcquire(int permits) {
return get(tryAcquireAsync(permits));
}
@Override
public RFuture tryAcquireAsync() {
return tryAcquireAsync(1);
}
@Override
public RFuture tryAcquireAsync(int permits) {
if (permits < 0) {
throw new IllegalArgumentException("Permits amount can't be negative");
}
if (permits == 0) {
return new CompletableFutureWrapper<>(true);
}
return commandExecutor.getServiceManager().execute(() -> {
RFuture future = tryAcquireAsync0(permits);
return commandExecutor.handleNoSync(future, () -> releaseAsync(permits));
});
}
private RFuture tryAcquireAsync0(int permits) {
return commandExecutor.syncedEvalNoRetry(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"local value = redis.call('get', KEYS[1]); " +
"if (value ~= false and tonumber(value) >= tonumber(ARGV[1])) then " +
"local val = redis.call('decrby', KEYS[1], ARGV[1]); " +
"return 1; " +
"end; " +
"return 0;",
Collections.