Support precise sub second timeout for BZPOPMIN, BZPOPMAX.

In cases where the timeout is defined in msec we calculate a precise timeout for the BZPOP* operations.
#2102 will bring this behaviour also to List commands.

Original Pull Request: #2088
This commit is contained in:
Christoph Strobl
2021-06-28 12:42:05 +02:00
parent 739b917922
commit bdf15de653
11 changed files with 96 additions and 33 deletions

View File

@@ -25,10 +25,10 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import org.reactivestreams.Publisher;
import org.springframework.data.domain.Range;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.redis.connection.ReactiveRedisConnection.CommandResponse;
@@ -163,8 +163,7 @@ public interface ReactiveZSetCommands {
}
/**
* Applies {@literal GT} mode. Constructs a new command
* instance with all previously configured properties.
* Applies {@literal GT} mode. Constructs a new command instance with all previously configured properties.
*
* @return a new {@link ZAddCommand} with {@literal incr} applied.
* @since 2.5
@@ -174,8 +173,7 @@ public interface ReactiveZSetCommands {
}
/**
* Applies {@literal LT} mode. Constructs a new command
* instance with all previously configured properties.
* Applies {@literal LT} mode. Constructs a new command instance with all previously configured properties.
*
* @return a new {@link ZAddCommand} with {@literal incr} applied.
* @since 2.5
@@ -206,7 +204,6 @@ public interface ReactiveZSetCommands {
}
/**
*
* @return {@literal true} if {@literal GT} is set.
* @since 2.5
*/
@@ -1337,15 +1334,18 @@ public interface ReactiveZSetCommands {
private final PopDirection direction;
private final Duration timeout;
private final @Nullable TimeUnit timeUnit;
private final @Nullable Long timeout;
private final long count;
private BZPopCommand(@Nullable ByteBuffer key, Duration timeout, long count, PopDirection direction) {
private BZPopCommand(@Nullable ByteBuffer key, @Nullable Long timeout, @Nullable TimeUnit timeUnit, long count,
PopDirection direction) {
super(key);
this.count = count;
this.timeout = timeout;
this.timeUnit = timeUnit;
this.direction = direction;
}
@@ -1355,7 +1355,7 @@ public interface ReactiveZSetCommands {
* @return a new {@link BZPopCommand} for min pop ({@literal ZPOPMIN}).
*/
public static BZPopCommand min() {
return new BZPopCommand(null, null, 0, PopDirection.MIN);
return new BZPopCommand(null, null, null, 0, PopDirection.MIN);
}
/**
@@ -1364,7 +1364,7 @@ public interface ReactiveZSetCommands {
* @return a new {@link BZPopCommand} for max pop ({@literal ZPOPMAX}).
*/
public static BZPopCommand max() {
return new BZPopCommand(null, null, 0, PopDirection.MAX);
return new BZPopCommand(null, null, null, 0, PopDirection.MAX);
}
/**
@@ -1377,7 +1377,7 @@ public interface ReactiveZSetCommands {
Assert.notNull(key, "Key must not be null!");
return new BZPopCommand(key, timeout, count, direction);
return new BZPopCommand(key, timeout, timeUnit, count, direction);
}
/**
@@ -1387,7 +1387,7 @@ public interface ReactiveZSetCommands {
* @return a new {@link BZPopCommand} with {@literal value} applied.
*/
public BZPopCommand count(long count) {
return new BZPopCommand(getKey(), timeout, count, direction);
return new BZPopCommand(getKey(), timeout, timeUnit, count, direction);
}
/**
@@ -1400,7 +1400,21 @@ public interface ReactiveZSetCommands {
Assert.notNull(timeout, "Timeout must not be null!");
return new BZPopCommand(getKey(), timeout, count, direction);
return blockingFor(timeout.toMillis(), TimeUnit.MILLISECONDS);
}
/**
* Applies a {@link Duration timeout}. Constructs a new command instance with all previously configured properties.
*
* @param timeout value.
* @param timeout must not be {@literal null}.
* @return a new {@link BZPopCommand} with {@link Duration timeout} applied.
*/
public BZPopCommand blockingFor(long timeout, TimeUnit timeUnit) {
Assert.notNull(timeUnit, "TimeUnit must not be null!");
return new BZPopCommand(getKey(), timeout, timeUnit, count, direction);
}
/**
@@ -1410,10 +1424,16 @@ public interface ReactiveZSetCommands {
return direction;
}
public Duration getTimeout() {
@Nullable
public Long getTimeout() {
return timeout;
}
@Nullable
public TimeUnit getTimeUnit() {
return timeUnit;
}
public long getCount() {
return count;
}
@@ -1664,7 +1684,7 @@ public interface ReactiveZSetCommands {
/**
* Creates a new {@link ZMScoreCommand} given a {@link List members}.
*
* @param member must not be {@literal null}.
* @param members must not be {@literal null}.
* @return a new {@link ZMScoreCommand} for {@link List} of members.
*/
public static ZMScoreCommand scoreOf(Collection<ByteBuffer> members) {

View File

@@ -997,8 +997,8 @@ public interface RedisZSetCommands {
Set<Tuple> zPopMin(byte[] key, long count);
/**
* Remove and return the value with its score having the lowest score from sorted set at {@code key}. <b>Blocks
* connection</b> until element available or {@code timeout} reached.
* Remove and return the value with its score having the lowest score from sorted set at {@code key}. <br />
* <b>Blocks connection</b> until element available or {@code timeout} reached.
*
* @param key must not be {@literal null}.
* @param timeout
@@ -1034,8 +1034,8 @@ public interface RedisZSetCommands {
Set<Tuple> zPopMax(byte[] key, long count);
/**
* Remove and return the value with its score having the highest score from sorted set at {@code key}. <b>Blocks
* connection</b> until element available or {@code timeout} reached.
* Remove and return the value with its score having the highest score from sorted set at {@code key}. <br />
* <b>Blocks connection</b> until element available or {@code timeout} reached.
*
* @param key must not be {@literal null}.
* @param timeout

View File

@@ -25,8 +25,8 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.ByteBuffer;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.reactivestreams.Publisher;
@@ -390,7 +390,18 @@ class LettuceReactiveZSetCommands implements ReactiveZSetCommands {
Assert.notNull(command.getKey(), "Key must not be null!");
Assert.notNull(command.getTimeout(), "Timeout must not be null!");
long timeout = command.getTimeout().get(ChronoUnit.SECONDS);
if(command.getTimeUnit() == TimeUnit.MILLISECONDS) {
double timeout = preciseTimeout(command.getTimeout(), command.getTimeUnit());
Mono<ScoredValue<ByteBuffer>> result = (command.getDirection() == PopDirection.MIN
? cmd.bzpopmin(timeout, command.getKey())
: cmd.bzpopmax(timeout, command.getKey())).filter(Value::hasValue).map(Value::getValue);
return new CommandResponse<>(command, result.filter(Value::hasValue).map(this::toTuple).flux());
}
long timeout = command.getTimeUnit().toSeconds(command.getTimeout());
Mono<ScoredValue<ByteBuffer>> result = (command.getDirection() == PopDirection.MIN
? cmd.bzpopmin(timeout, command.getKey())
@@ -624,6 +635,10 @@ class LettuceReactiveZSetCommands implements ReactiveZSetCommands {
return new DefaultTuple(ByteUtils.getBytes(value), score);
}
static double preciseTimeout(long val, TimeUnit unit) {
return (double) unit.toMillis(val) / 1000.0D;
}
protected LettuceReactiveRedisConnection getConnection() {
return connection;
}

View File

@@ -316,6 +316,13 @@ class LettuceZSetCommands implements RedisZSetCommands {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(unit, "TimeUnit must not be null!");
if(TimeUnit.MILLISECONDS == unit) {
return connection.invoke(connection.getAsyncDedicatedConnection())
.from(RedisSortedSetAsyncCommands::bzpopmin, preciseTimeout(timeout, unit), key)
.get(it -> it.map(LettuceConverters::toTuple).getValueOrElse(null));
}
return connection.invoke(connection.getAsyncDedicatedConnection())
.from(RedisSortedSetAsyncCommands::bzpopmin, unit.toSeconds(timeout), key)
.get(it -> it.map(LettuceConverters::toTuple).getValueOrElse(null));
@@ -359,6 +366,13 @@ class LettuceZSetCommands implements RedisZSetCommands {
Assert.notNull(key, "Key must not be null!");
Assert.notNull(unit, "TimeUnit must not be null!");
if(TimeUnit.MILLISECONDS == unit) {
return connection.invoke(connection.getAsyncDedicatedConnection())
.from(RedisSortedSetAsyncCommands::bzpopmax, preciseTimeout(timeout, unit), key)
.get(it -> it.map(LettuceConverters::toTuple).getValueOrElse(null));
}
return connection.invoke(connection.getAsyncDedicatedConnection())
.from(RedisSortedSetAsyncCommands::bzpopmax, unit.toSeconds(timeout), key)
.get(it -> it.map(LettuceConverters::toTuple).getValueOrElse(null));
@@ -703,4 +717,7 @@ class LettuceZSetCommands implements RedisZSetCommands {
return target;
}
static double preciseTimeout(long val, TimeUnit unit) {
return (double) unit.toMillis(val) / 1000.0D;
}
}

View File

@@ -365,8 +365,8 @@ public interface ZSetOperations<K, V> {
Set<TypedTuple<V>> popMin(K key, long count);
/**
* Remove and return the value with its score having the lowest score from sorted set at {@code key}. <b>Blocks
* connection</b> until element available or {@code timeout} reached.
* Remove and return the value with its score having the lowest score from sorted set at {@code key}. <br />
* <b>Blocks connection</b> until element available or {@code timeout} reached.
*
* @param key must not be {@literal null}.
* @param timeout
@@ -379,8 +379,8 @@ public interface ZSetOperations<K, V> {
TypedTuple<V> popMin(K key, long timeout, TimeUnit unit);
/**
* Remove and return the value with its score having the lowest score from sorted set at {@code key}. <b>Blocks
* connection</b> until element available or {@code timeout} reached.
* Remove and return the value with its score having the lowest score from sorted set at {@code key}. <br />
* <b>Blocks connection</b> until element available or {@code timeout} reached.
*
* @param key must not be {@literal null}.
* @param timeout must not be {@literal null}.
@@ -422,8 +422,8 @@ public interface ZSetOperations<K, V> {
Set<TypedTuple<V>> popMax(K key, long count);
/**
* Remove and return the value with its score having the highest score from sorted set at {@code key}. <b>Blocks
* connection</b> until element available or {@code timeout} reached.
* Remove and return the value with its score having the highest score from sorted set at {@code key}. <br />
* <b>Blocks connection</b> until element available or {@code timeout} reached.
*
* @param key must not be {@literal null}.
* @param timeout
@@ -436,8 +436,8 @@ public interface ZSetOperations<K, V> {
TypedTuple<V> popMax(K key, long timeout, TimeUnit unit);
/**
* Remove and return the value with its score having the highest score from sorted set at {@code key}. <b>Blocks
* connection</b> until element available or {@code timeout} reached.
* Remove and return the value with its score having the highest score from sorted set at {@code key}. <br />
* <b>Blocks connection</b> until element available or {@code timeout} reached.
*
* @param key must not be {@literal null}.
* @param timeout must not be {@literal null}.