org.redisson.transaction.RedissonTransactionalBucket Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of redisson Show documentation
Show all versions of redisson Show documentation
Redis Java client with features of In-Memory Data Grid
/**
* 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.transaction;
import io.netty.buffer.ByteBuf;
import org.redisson.RedissonBucket;
import org.redisson.api.RFuture;
import org.redisson.api.RLock;
import org.redisson.client.codec.Codec;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.misc.CompletableFutureWrapper;
import org.redisson.transaction.operation.*;
import org.redisson.transaction.operation.bucket.*;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
/**
*
* @author Nikita Koksharov
*
* @param value type
*/
public class RedissonTransactionalBucket extends RedissonBucket {
static final Object NULL = new Object();
private long timeout;
private final AtomicBoolean executed;
private final List operations;
private Object state;
private boolean hasExpiration;
private final String transactionId;
public RedissonTransactionalBucket(CommandAsyncExecutor commandExecutor, long timeout, String name, List operations, AtomicBoolean executed, String transactionId) {
super(commandExecutor, name);
this.operations = operations;
this.executed = executed;
this.transactionId = transactionId;
this.timeout = timeout;
}
public RedissonTransactionalBucket(Codec codec, CommandAsyncExecutor commandExecutor, long timeout, String name, List operations, AtomicBoolean executed, String transactionId) {
super(codec, commandExecutor, name);
this.operations = operations;
this.executed = executed;
this.transactionId = transactionId;
this.timeout = timeout;
}
@Override
protected RFuture expireAsync(long timeToLive, TimeUnit timeUnit, String param, String... keys) {
checkState();
long currentThreadId = Thread.currentThread().getId();
return executeLocked(() -> {
if (state != null) {
operations.add(new ExpireOperation(getName(), getLockName(), currentThreadId, transactionId, timeToLive, timeUnit, param, keys));
hasExpiration = true;
return CompletableFuture.completedFuture(state != NULL);
}
return isExistsAsync().thenApply(res -> {
operations.add(new ExpireOperation(getName(), getLockName(), currentThreadId, transactionId, timeToLive, timeUnit, param, keys));
hasExpiration = res;
return res;
});
});
}
@Override
protected RFuture expireAtAsync(long timestamp, String param, String... keys) {
checkState();
long currentThreadId = Thread.currentThread().getId();
return executeLocked(() -> {
if (state != null) {
operations.add(new ExpireAtOperation(getName(), getLockName(), currentThreadId, transactionId, timestamp, param, keys));
hasExpiration = true;
return CompletableFuture.completedFuture(state != NULL);
}
return isExistsAsync().thenApply(res -> {
operations.add(new ExpireAtOperation(getName(), getLockName(), currentThreadId, transactionId, timestamp, param, keys));
hasExpiration = res;
return res;
});
});
}
@Override
public RFuture clearExpireAsync() {
checkState();
long currentThreadId = Thread.currentThread().getId();
return executeLocked(() -> {
if (hasExpiration) {
operations.add(new ClearExpireOperation(getName(), getLockName(), currentThreadId, transactionId));
hasExpiration = false;
return CompletableFuture.completedFuture(true);
}
return remainTimeToLiveAsync().thenApply(res -> {
operations.add(new ClearExpireOperation(getName(), getLockName(), currentThreadId, transactionId));
hasExpiration = false;
return res > 0;
});
});
}
@Override
public RFuture moveAsync(int database) {
throw new UnsupportedOperationException("moveAsync method is not supported in transaction");
}
@Override
public RFuture migrateAsync(String host, int port, int database, long timeout) {
throw new UnsupportedOperationException("migrateAsync method is not supported in transaction");
}
@Override
public RFuture sizeAsync() {
checkState();
if (state != null) {
if (state == NULL) {
return new CompletableFutureWrapper<>(0L);
} else {
ByteBuf buf = encode(state);
long size = buf.readableBytes();
buf.release();
return new CompletableFutureWrapper<>(size);
}
}
return super.sizeAsync();
}
@Override
public RFuture isExistsAsync() {
checkState();
if (state != null) {
return new CompletableFutureWrapper<>(state != NULL);
}
return super.isExistsAsync();
}
@Override
public RFuture touchAsync() {
checkState();
long currentThreadId = Thread.currentThread().getId();
return executeLocked(() -> {
if (state != null) {
operations.add(new TouchOperation(getName(), getLockName(), currentThreadId, transactionId));
return CompletableFuture.completedFuture(state != NULL);
}
return isExistsAsync().thenApply(res -> {
operations.add(new TouchOperation(getName(), getLockName(), currentThreadId, transactionId));
return res;
});
});
}
@Override
public RFuture unlinkAsync() {
checkState();
long currentThreadId = Thread.currentThread().getId();
return executeLocked(() -> {
if (state != null) {
operations.add(new UnlinkOperation(getName(), getLockName(), currentThreadId, transactionId));
if (state == NULL) {
return CompletableFuture.completedFuture(false);
} else {
state = NULL;
return CompletableFuture.completedFuture(true);
}
}
return isExistsAsync().thenApply(res -> {
operations.add(new UnlinkOperation(getName(), getLockName(), currentThreadId, transactionId));
state = NULL;
return res;
});
});
}
@Override
public RFuture deleteAsync() {
checkState();
long threadId = Thread.currentThread().getId();
return executeLocked(() -> {
if (state != null) {
operations.add(new DeleteOperation(getName(), getLockName(), transactionId, threadId));
if (state == NULL) {
return CompletableFuture.completedFuture(false);
} else {
state = NULL;
return CompletableFuture.completedFuture(true);
}
}
return isExistsAsync().thenApply(res -> {
operations.add(new DeleteOperation(getName(), getLockName(), transactionId, threadId));
state = NULL;
return res;
});
});
}
@Override
@SuppressWarnings("unchecked")
public RFuture getAsync() {
checkState();
if (state != null) {
if (state == NULL) {
return new CompletableFutureWrapper<>((V) null);
} else {
return new CompletableFutureWrapper<>((V) state);
}
}
return super.getAsync();
}
@Override
public RFuture compareAndSetAsync(V expect, V update) {
checkState();
long currentThreadId = Thread.currentThread().getId();
return executeLocked(() -> {
if (state != null) {
operations.add(new BucketCompareAndSetOperation(getName(), getLockName(), getCodec(), expect, update, transactionId, currentThreadId));
if ((state == NULL && expect == null)
|| isEquals(state, expect)) {
state = Optional.ofNullable((Object) update).orElse(NULL);
return CompletableFuture.completedFuture(true);
}
return CompletableFuture.completedFuture(false);
}
return getAsync().thenApply(res -> {
operations.add(new BucketCompareAndSetOperation(getName(), getLockName(), getCodec(), expect, update, transactionId, currentThreadId));
if ((res == null && expect == null)
|| isEquals(res, expect)) {
state = Optional.ofNullable((Object) update).orElse(NULL);
return true;
}
return false;
});
});
}
@Override
public RFuture getAndSetAsync(V value, long timeToLive, TimeUnit timeUnit) {
return getAndSet(value, new BucketGetAndSetOperation(getName(), getLockName(), getCodec(), value, timeToLive, timeUnit, transactionId));
}
@Override
public RFuture getAndSetAsync(V value) {
return getAndSet(value, new BucketGetAndSetOperation(getName(), getLockName(), getCodec(), value, transactionId));
}
@SuppressWarnings("unchecked")
private RFuture getAndSet(V newValue, TransactionalOperation operation) {
checkState();
return executeLocked(() -> {
if (state != null) {
Object prevValue = Optional.of(state).filter(s -> s != NULL).orElse(null);
operations.add(operation);
state = Optional.ofNullable((Object) newValue).orElse(NULL);
return CompletableFuture.completedFuture((V) prevValue);
}
return getAsync().thenApply(res -> {
state = Optional.ofNullable((Object) newValue).orElse(NULL);
operations.add(operation);
return res;
});
});
}
@Override
@SuppressWarnings("unchecked")
public RFuture getAndDeleteAsync() {
checkState();
long currentThreadId = Thread.currentThread().getId();
return executeLocked(() -> {
if (state != null) {
Object prevValue = Optional.of(state).filter(s -> s != NULL).orElse(null);
operations.add(new BucketGetAndDeleteOperation(getName(), getLockName(), getCodec(), transactionId, currentThreadId));
state = NULL;
return CompletableFuture.completedFuture((V) prevValue);
}
return getAsync().thenApply(res -> {
state = NULL;
operations.add(new BucketGetAndDeleteOperation(getName(), getLockName(), getCodec(), transactionId, currentThreadId));
return res;
});
});
}
@Override
public RFuture setAsync(V newValue) {
long currentThreadId = Thread.currentThread().getId();
return setAsync(newValue, new BucketSetOperation(getName(), getLockName(), getCodec(), newValue, transactionId, currentThreadId));
}
private RFuture setAsync(V newValue, BucketSetOperation operation) {
checkState();
return executeLocked(() -> {
hasExpiration = operation.getTimeUnit() != null;
operations.add(operation);
state = Optional.ofNullable((Object) newValue).orElse(NULL);
return CompletableFuture.completedFuture(null);
});
}
@Override
public RFuture setAsync(V value, long timeToLive, TimeUnit timeUnit) {
long currentThreadId = Thread.currentThread().getId();
return setAsync(value, new BucketSetOperation(getName(), getLockName(), getCodec(), value, timeToLive, timeUnit, transactionId, currentThreadId));
}
@Override
public RFuture setAsync(V value, Duration duration) {
long currentThreadId = Thread.currentThread().getId();
return setAsync(value, new BucketSetOperation(getName(), getLockName(), getCodec(), value, duration.toMillis(), TimeUnit.MILLISECONDS, transactionId, currentThreadId));
}
@Override
public RFuture trySetAsync(V newValue) {
long currentThreadId = Thread.currentThread().getId();
return trySet(newValue, new BucketTrySetOperation(getName(), getLockName(), getCodec(), newValue, transactionId, currentThreadId));
}
@Override
public RFuture trySetAsync(V value, long timeToLive, TimeUnit timeUnit) {
long currentThreadId = Thread.currentThread().getId();
return trySet(value, new BucketTrySetOperation(getName(), getLockName(), getCodec(), value, timeToLive, timeUnit, transactionId, currentThreadId));
}
private RFuture trySet(V newValue, BucketTrySetOperation operation) {
checkState();
return executeLocked(() -> {
if (state != null) {
operations.add(operation);
if (state == NULL) {
state = Optional.ofNullable((Object) newValue).orElse(NULL);
hasExpiration = operation.getTimeUnit() != null;
return CompletableFuture.completedFuture(true);
} else {
return CompletableFuture.completedFuture(false);
}
}
return getAsync().thenApply(res -> {
operations.add(operation);
if (res == null) {
hasExpiration = operation.getTimeUnit() != null;
state = Optional.ofNullable((Object) newValue).orElse(NULL);
return true;
}
return false;
});
});
}
private boolean isEquals(Object value, Object oldValue) {
ByteBuf valueBuf = encode(value);
ByteBuf oldValueBuf = encode(oldValue);
try {
return valueBuf.equals(oldValueBuf);
} finally {
valueBuf.readableBytes();
oldValueBuf.readableBytes();
}
}
protected RFuture executeLocked(Supplier> runnable) {
RLock lock = getLock();
CompletionStage f = lock.lockAsync(timeout, TimeUnit.MILLISECONDS).thenCompose(res -> runnable.get());
return new CompletableFutureWrapper<>(f);
}
private RLock getLock() {
return new RedissonTransactionalLock(commandExecutor, getLockName(), transactionId);
}
private String getLockName() {
return getName() + ":transaction_lock";
}
protected void checkState() {
if (executed.get()) {
throw new IllegalStateException("Unable to execute operation. Transaction is in finished state!");
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy