org.redisson.RedissonPriorityQueue 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;
import org.redisson.api.*;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.StringCodec;
import org.redisson.client.protocol.RedisCommand;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandAsyncExecutor;
import org.redisson.misc.CompletableFutureWrapper;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
*
* @author Nikita Koksharov
*
* @param value type
*/
public class RedissonPriorityQueue extends BaseRedissonList implements RPriorityQueue {
public static class BinarySearchResult {
private V value;
private int index = -1;
public BinarySearchResult(V value) {
super();
this.value = value;
}
public BinarySearchResult() {
}
public void setIndex(int index) {
this.index = index;
}
public int getIndex() {
return index;
}
public V getValue() {
return value;
}
}
private Comparator comparator = Comparator.naturalOrder();
CommandAsyncExecutor commandExecutor;
RLock lock;
private RBucket comparatorHolder;
public RedissonPriorityQueue(CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) {
super(commandExecutor, name, redisson);
this.commandExecutor = commandExecutor;
comparatorHolder = redisson.getBucket(getComparatorKeyName(), StringCodec.INSTANCE);
lock = redisson.getLock(getLockName());
}
public RedissonPriorityQueue(Codec codec, CommandAsyncExecutor commandExecutor, String name, RedissonClient redisson) {
super(codec, commandExecutor, name, redisson);
this.commandExecutor = commandExecutor;
comparatorHolder = redisson.getBucket(getComparatorKeyName(), StringCodec.INSTANCE);
lock = redisson.getLock(getLockName());
}
private String getLockName() {
return prefixName("redisson_sortedset_lock", getName());
}
private void loadComparator() {
try {
String comparatorSign = comparatorHolder.get();
if (comparatorSign != null) {
String[] parts = comparatorSign.split(":");
String className = parts[0];
String sign = parts[1];
String result = calcClassSign(className);
if (!result.equals(sign)) {
throw new IllegalStateException("Local class signature of " + className + " differs from used by this SortedSet!");
}
Class> clazz = Class.forName(className);
comparator = (Comparator) clazz.newInstance();
} else {
throw new IllegalStateException("Comparator is not set!");
}
} catch (IllegalStateException e) {
throw e;
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
// TODO cache result
private static String calcClassSign(String name) {
try {
Class> clazz = Class.forName(name);
ByteArrayOutputStream result = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(result);
outputStream.writeObject(clazz);
outputStream.close();
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(result.toByteArray());
return new BigInteger(1, crypt.digest()).toString(16);
} catch (Exception e) {
throw new IllegalStateException("Can't calculate sign of " + name, e);
}
}
@Override
public boolean offer(V e) {
return add(e);
}
@Override
public boolean contains(Object o) {
checkComparator();
return binarySearch((V) o).getIndex() >= 0;
}
@Override
public boolean add(V value) {
lock.lock();
try {
checkComparator();
BinarySearchResult res = binarySearch(value);
int index = 0;
if (res.getIndex() < 0) {
index = -(res.getIndex() + 1);
} else {
index = res.getIndex() + 1;
}
get(commandExecutor.evalWriteNoRetryAsync(getRawName(), codec, RedisCommands.EVAL_VOID,
"local len = redis.call('llen', KEYS[1]);"
+ "if tonumber(ARGV[1]) < len then "
+ "local pivot = redis.call('lindex', KEYS[1], ARGV[1]);"
+ "redis.call('linsert', KEYS[1], 'before', pivot, ARGV[2]);"
+ "return;"
+ "end;"
+ "redis.call('rpush', KEYS[1], ARGV[2]);",
Arrays.asList(getRawName()),
index, encode(value)));
return true;
} finally {
lock.unlock();
}
}
private void checkComparator() {
String comparatorSign = comparatorHolder.get();
if (comparatorSign != null) {
String[] vals = comparatorSign.split(":");
String className = vals[0];
if (!comparator.getClass().getName().equals(className)) {
loadComparator();
}
}
}
@Override
public boolean remove(Object value) {
lock.lock();
try {
checkComparator();
BinarySearchResult res = binarySearch((V) value);
if (res.getIndex() < 0) {
return false;
}
remove((int) res.getIndex());
return true;
} finally {
lock.unlock();
}
}
@Override
public boolean containsAll(Collection> c) {
checkComparator();
for (Object object : c) {
if (binarySearch((V) object).getIndex() < 0) {
return false;
}
}
return true;
}
@Override
public boolean addAll(Collection extends V> c) {
boolean changed = false;
for (V v : c) {
if (add(v)) {
changed = true;
}
}
return changed;
}
@Override
public boolean retainAll(Collection> c) {
boolean changed = false;
for (Iterator> iterator = iterator(); iterator.hasNext();) {
Object object = (Object) iterator.next();
if (!c.contains(object)) {
iterator.remove();
changed = true;
}
}
return changed;
}
@Override
public boolean removeAll(Collection> c) {
boolean changed = false;
for (Object obj : c) {
if (remove(obj)) {
changed = true;
}
}
return changed;
}
@Override
public void clear() {
delete();
}
@Override
public Comparator super V> comparator() {
return comparator;
}
@Override
public RFuture pollAsync() {
return wrapLockedAsync(RedisCommands.LPOP, getRawName());
}
protected final RFuture wrapLockedAsync(RedisCommand command, Object... params) {
return wrapLockedAsync(() -> {
return commandExecutor.writeAsync(getRawName(), codec, command, params);
});
}
protected final RFuture wrapLockedAsync(Supplier> callable) {
long randomId = getServiceManager().generateValue();
CompletionStage f = lock.lockAsync(randomId).thenCompose(r -> {
RFuture callback = callable.get();
return callback.handle((value, ex) -> {
CompletableFuture result = new CompletableFuture<>();
lock.unlockAsync(randomId)
.whenComplete((r2, ex2) -> {
if (ex2 != null) {
if (ex != null) {
ex2.addSuppressed(ex);
}
result.completeExceptionally(ex2);
return;
}
if (ex != null) {
result.completeExceptionally(ex);
return;
}
result.complete(value);
});
return result;
}).thenCompose(ff -> ff);
});
return new CompletableFutureWrapper<>(f);
}
public V getFirst() {
V value = getValue(0);
if (value == null) {
throw new NoSuchElementException();
}
return value;
}
@Override
public V poll() {
return get(pollAsync());
}
@Override
public V element() {
return getFirst();
}
@Override
public RFuture peekAsync() {
return getAsync(0);
}
@Override
public V peek() {
return getValue(0);
}
private String getComparatorKeyName() {
return suffixName(getRawName(), "redisson_sortedset_comparator");
}
@Override
public boolean trySetComparator(Comparator super V> comparator) {
String className = comparator.getClass().getName();
String comparatorSign = className + ":" + calcClassSign(className);
Boolean res = get(commandExecutor.writeAsync(getRawName(), StringCodec.INSTANCE, RedisCommands.SETNX, getComparatorKeyName(), comparatorSign));
if (res) {
this.comparator = comparator;
}
return res;
}
@Override
public V remove() {
return removeFirst();
}
public V removeFirst() {
V value = poll();
if (value == null) {
throw new NoSuchElementException();
}
return value;
}
// TODO optimize: get three values each time instead of single
public BinarySearchResult binarySearch(V value) {
int size = size();
int upperIndex = size - 1;
int lowerIndex = 0;
while (lowerIndex <= upperIndex) {
int index = lowerIndex + (upperIndex - lowerIndex) / 2;
V res = getValue(index);
if (res == null) {
return new BinarySearchResult();
}
int cmp = comparator.compare(value, res);
if (cmp == 0) {
BinarySearchResult indexRes = new BinarySearchResult();
indexRes.setIndex(index);
return indexRes;
} else if (cmp < 0) {
upperIndex = index - 1;
} else {
lowerIndex = index + 1;
}
}
BinarySearchResult indexRes = new BinarySearchResult();
indexRes.setIndex(-(lowerIndex + 1));
return indexRes;
}
@Override
@SuppressWarnings("AvoidInlineConditionals")
public String toString() {
Iterator it = iterator();
if (! it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
V e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
@Override
public V pollLastAndOfferFirstTo(String queueName) {
return get(pollLastAndOfferFirstToAsync(queueName));
}
@Override
public RFuture pollLastAndOfferFirstToAsync(String queueName) {
return wrapLockedAsync(RedisCommands.RPOPLPUSH, getRawName(), queueName);
}
@Override
public RFuture deleteAsync() {
return deleteAsync(getRawName(), getComparatorKeyName());
}
@Override
public RFuture expireAsync(long timeToLive, TimeUnit timeUnit, String param, String... keys) {
return super.expireAsync(timeToLive, timeUnit, param, getRawName(), getComparatorKeyName());
}
@Override
protected RFuture expireAtAsync(long timestamp, String param, String... keys) {
return super.expireAtAsync(timestamp, param, getRawName(), getComparatorKeyName());
}
@Override
public RFuture clearExpireAsync() {
return clearExpireAsync(getRawName(), getComparatorKeyName());
}
@Override
public List poll(int limit) {
return get(pollAsync(limit));
}
@Override
public RFuture offerAsync(V e) {
throw new UnsupportedOperationException();
}
@Override
public RFuture addAsync(V e) {
throw new UnsupportedOperationException();
}
@Override
public RFuture> pollAsync(int limit) {
return wrapLockedAsync(() -> {
return commandExecutor.evalWriteNoRetryAsync(getRawName(), codec, RedisCommands.EVAL_LIST,
"local result = {};"
+ "for i = 1, ARGV[1], 1 do " +
"local value = redis.call('lpop', KEYS[1]);" +
"if value ~= false then " +
"table.insert(result, value);" +
"else " +
"return result;" +
"end;" +
"end; " +
"return result;",
Collections.singletonList(getRawName()), limit);
});
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy