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-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.ByteBufUtil;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
import org.redisson.api.RFuture;
import org.redisson.api.RPermitExpirableSemaphore;
import org.redisson.client.codec.ByteArrayCodec;
import org.redisson.client.codec.LongCodec;
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.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
/**
*
* @author Nikita Koksharov
*
*/
public class RedissonPermitExpirableSemaphore extends RedissonExpirable implements RPermitExpirableSemaphore {
private static final Logger LOGGER = LoggerFactory.getLogger(RedissonPermitExpirableSemaphore.class);
private final String channelName;
private final SemaphorePubSub semaphorePubSub;
private final String timeoutName;
private final long nonExpirableTimeout = 922337203685477L;
public RedissonPermitExpirableSemaphore(CommandAsyncExecutor commandExecutor, String name) {
super(commandExecutor, name);
this.timeoutName = suffixName(getRawName(), "timeout");
this.semaphorePubSub = commandExecutor.getConnectionManager().getSubscribeService().getSemaphorePubSub();
this.channelName = prefixName("redisson_sc", getRawName());
}
@Override
public String acquire() throws InterruptedException {
return acquire(-1, TimeUnit.MILLISECONDS);
}
@Override
public List acquire(int permits) throws InterruptedException {
return acquire(permits, -1, TimeUnit.MILLISECONDS);
}
@Override
public String acquire(long leaseTime, TimeUnit timeUnit) throws InterruptedException {
List ids = acquire(1, leaseTime, timeUnit);
return getFirstOrNull(ids);
}
@Override
public List acquire(int permits, long leaseTime, TimeUnit timeUnit) throws InterruptedException {
List ids = tryAcquire(permits, leaseTime, timeUnit);
if (!ids.isEmpty() && !hasOnlyNearestTimeout(ids)) {
return ids;
}
CompletableFuture future = subscribe();
semaphorePubSub.timeout(future);
RedissonLockEntry entry = commandExecutor.getInterrupted(future);
try {
while (true) {
Long nearestTimeout;
ids = tryAcquire(permits, leaseTime, timeUnit);
if (ids.isEmpty()) {
nearestTimeout = null;
} else if (hasOnlyNearestTimeout(ids)) {
nearestTimeout = Long.parseLong(ids.get(0).substring(1)) - System.currentTimeMillis();
} else {
return ids;
}
if (nearestTimeout != null) {
entry.getLatch().tryAcquire(permits, nearestTimeout, TimeUnit.MILLISECONDS);
} else {
entry.getLatch().acquire(permits);
}
}
} finally {
unsubscribe(entry);
}
// return get(acquireAsync(permits, leaseTime, timeUnit));
}
@Override
public RFuture acquireAsync() {
CompletionStage future = acquireAsync(1)
.thenApply(RedissonPermitExpirableSemaphore::getFirstOrNull);
return new CompletableFutureWrapper<>(future);
}
@Override
public RFuture> acquireAsync(int permits) {
return acquireAsync(permits, -1, TimeUnit.MILLISECONDS);
}
@Override
public RFuture acquireAsync(long leaseTime, TimeUnit timeUnit) {
CompletionStage future = acquireAsync(1, leaseTime, timeUnit)
.thenApply(RedissonPermitExpirableSemaphore::getFirstOrNull);
return new CompletableFutureWrapper<>(future);
}
@Override
public RFuture> acquireAsync(int permits, long leaseTime, TimeUnit timeUnit) {
long timeoutDate = calcTimeout(leaseTime, timeUnit);
RFuture> tryAcquireFuture = tryAcquireAsync(permits, timeoutDate);
CompletionStage> f = tryAcquireFuture.thenCompose(ids -> {
if (!ids.isEmpty() && !hasOnlyNearestTimeout(ids)) {
return CompletableFuture.completedFuture(ids);
}
CompletableFuture subscribeFuture = subscribe();
semaphorePubSub.timeout(subscribeFuture);
return subscribeFuture.thenCompose(res -> acquireAsync(permits, res, leaseTime, timeUnit));
});
f.whenComplete((r, e) -> {
if (f.toCompletableFuture().isCancelled()) {
tryAcquireFuture.whenComplete((ids, ex) -> {
if (!ids.isEmpty() && !hasOnlyNearestTimeout(ids)) {
releaseAsync(ids);
}
});
}
});
return new CompletableFutureWrapper<>(f);
}
private void tryAcquireAsync(AtomicLong time, int permits, RedissonLockEntry entry, CompletableFuture> result, long leaseTime, TimeUnit timeUnit) {
if (result.isDone()) {
unsubscribe(entry);
return;
}
if (time.get() <= 0) {
unsubscribe(entry);
result.complete(Collections.emptyList());
return;
}
long timeoutDate = calcTimeout(leaseTime, timeUnit);
long curr = System.currentTimeMillis();
RFuture> tryAcquireFuture = tryAcquireAsync(permits, timeoutDate);
tryAcquireFuture.whenComplete((ids, e) -> {
if (e != null) {
unsubscribe(entry);
result.completeExceptionally(e);
return;
}
Long nearestTimeout;
if (ids.isEmpty()) {
nearestTimeout = null;
} else if (hasOnlyNearestTimeout(ids)) {
nearestTimeout = Long.parseLong(ids.get(0).substring(1)) - System.currentTimeMillis();
} else {
unsubscribe(entry);
if (!result.complete(ids)) {
releaseAsync(ids);
}
return;
}
long el = System.currentTimeMillis() - curr;
time.addAndGet(-el);
if (time.get() <= 0) {
unsubscribe(entry);
result.complete(Collections.emptyList());
return;
}
// waiting for message
long current = System.currentTimeMillis();
if (entry.getLatch().tryAcquire()) {
tryAcquireAsync(time, permits, entry, result, leaseTime, timeUnit);
} else {
AtomicReference waitTimeoutFutureRef = new AtomicReference<>();
Timeout scheduledFuture;
if (nearestTimeout != null) {
scheduledFuture = getServiceManager().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, entry, result, leaseTime, 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, entry, result, leaseTime, timeUnit);
};
entry.addListener(listener);
long t = time.get();
Timeout waitTimeoutFuture = getServiceManager().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, entry, result, leaseTime, timeUnit);
}
}
}, t, TimeUnit.MILLISECONDS);
waitTimeoutFutureRef.set(waitTimeoutFuture);
}
});
}
private CompletableFuture> acquireAsync(int permits, RedissonLockEntry entry, long leaseTime, TimeUnit timeUnit) {
long timeoutDate = calcTimeout(leaseTime, timeUnit);
CompletableFuture> tryAcquireFuture = tryAcquireAsync(permits, timeoutDate).toCompletableFuture();
return tryAcquireFuture.whenComplete((p, e) -> {
if (e != null) {
unsubscribe(entry);
}
}).thenCompose(ids -> {
Long nearestTimeout;
if (ids.isEmpty()) {
nearestTimeout = null;
} else if (hasOnlyNearestTimeout(ids)) {
nearestTimeout = Long.parseLong(ids.get(0).substring(1)) - System.currentTimeMillis();
} else {
unsubscribe(entry);
return CompletableFuture.completedFuture(ids);
}
if (entry.getLatch().tryAcquire(permits)) {
return acquireAsync(permits, entry, leaseTime, timeUnit);
}
CompletableFuture> res = new CompletableFuture<>();
Timeout scheduledFuture;
if (nearestTimeout != null) {
scheduledFuture = getServiceManager().newTimeout(timeout -> {
CompletableFuture> r = acquireAsync(permits, entry, leaseTime, timeUnit);
commandExecutor.transfer(r, res);
}, nearestTimeout, TimeUnit.MILLISECONDS);
} else {
scheduledFuture = null;
}
Runnable listener = () -> {
if (scheduledFuture != null && !scheduledFuture.cancel()) {
entry.getLatch().release();
return;
}
CompletableFuture> r = acquireAsync(permits, entry, leaseTime, timeUnit);
commandExecutor.transfer(r, res);
};
entry.addListener(listener);
return res;
});
}
@Override
public String tryAcquire() {
List ids = tryAcquire(1);
return getFirstOrNull(ids);
}
@Override
public List tryAcquire(int permits) {
List ids = tryAcquire(permits, -1, TimeUnit.MILLISECONDS);
if (hasOnlyNearestTimeout(ids)) {
return Collections.emptyList();
}
return ids;
}
private List tryAcquire(int permits, long leaseTime, TimeUnit timeUnit) {
long timeoutDate = calcTimeout(leaseTime, timeUnit);
return get(tryAcquireAsync(permits, timeoutDate));
}
private long calcTimeout(long leaseTime, TimeUnit timeUnit) {
if (leaseTime != -1) {
return System.currentTimeMillis() + timeUnit.toMillis(leaseTime);
}
return nonExpirableTimeout;
}
@Override
public RFuture tryAcquireAsync() {
CompletionStage future = tryAcquireAsync(1)
.thenApply(RedissonPermitExpirableSemaphore::getFirstOrNull);
return new CompletableFutureWrapper<>(future);
}
@Override
public RFuture> tryAcquireAsync(int permits) {
CompletableFuture> future = tryAcquireAsync(permits, nonExpirableTimeout).toCompletableFuture()
.thenApply(ids -> {
if (hasOnlyNearestTimeout(ids)) {
return null;
}
return ids;
});
future.whenComplete((ids, e) -> {
if (future.isCancelled() && !ids.isEmpty() && !hasOnlyNearestTimeout(ids)) {
releaseAsync(ids);
}
});
return new CompletableFutureWrapper<>(future);
}
private RFuture> tryAcquireAsync(int permits, long timeoutDate) {
if (permits < 0) {
throw new IllegalArgumentException("Permits amount can't be negative");
}
List ids = new ArrayList<>(permits);
for (int i = 0; i < permits; i++) {
ids.add(getServiceManager().generateId());
}
return getServiceManager().execute(() -> {
RFuture> future = tryAcquireAsync(ids, timeoutDate);
return commandExecutor.handleNoSync(future, () -> releaseAsync(ids));
});
}
private RFuture> tryAcquireAsync(List ids, long timeoutDate) {
List
© 2015 - 2024 Weber Informatics LLC | Privacy Policy