DATAREDIS-531 - Scan commands should use a dedicated bound connection.

SCAN, SSCAN, HSCAN and ZSCAN now reference a dedicated connection that is bound to the Cursor. This allows creation of multiple cursors for different threads without the risk of potentially sharing a single connection. As before the caller is responsible for closing the Cursor correctly after usage.

Original pull request: #218.
This commit is contained in:
Christoph Strobl
2016-08-31 12:28:04 +02:00
committed by Mark Paluch
parent 5fd719bd8a
commit 58d23dea37
20 changed files with 473 additions and 34 deletions

View File

@@ -3790,6 +3790,10 @@ public class JedisConnection extends AbstractRedisConnection {
JedisConverters.stringListToByteList().convert(result.getResult()));
}
protected void doClose() {
JedisConnection.this.close();
};
}.open();
}
@@ -3828,6 +3832,10 @@ public class JedisConnection extends AbstractRedisConnection {
JedisConverters.tuplesToTuples().convert(result.getResult()));
}
protected void doClose() {
JedisConnection.this.close();
};
}.open();
}
@@ -3863,6 +3871,10 @@ public class JedisConnection extends AbstractRedisConnection {
redis.clients.jedis.ScanResult<byte[]> result = jedis.sscan(key, JedisConverters.toBytes(cursorId), params);
return new ScanIteration<byte[]>(Long.valueOf(result.getStringCursor()), result.getResult());
}
protected void doClose() {
JedisConnection.this.close();
};
}.open();
}
@@ -3898,6 +3910,11 @@ public class JedisConnection extends AbstractRedisConnection {
ScanResult<Entry<byte[], byte[]>> result = jedis.hscan(key, JedisConverters.toBytes(cursorId), params);
return new ScanIteration<Map.Entry<byte[], byte[]>>(Long.valueOf(result.getStringCursor()), result.getResult());
}
protected void doClose() {
JedisConnection.this.close();
};
}.open();
}

View File

@@ -3719,6 +3719,11 @@ public class LettuceConnection extends AbstractRedisConnection {
return new ScanIteration<byte[]>(Long.valueOf(nextCursorId), (keys));
}
protected void doClose() {
LettuceConnection.this.close();
}
}.open();
}
@@ -3759,6 +3764,10 @@ public class LettuceConnection extends AbstractRedisConnection {
return new ScanIteration<Entry<byte[], byte[]>>(Long.valueOf(nextCursorId), values.entrySet());
}
protected void doClose() {
LettuceConnection.this.close();
}
}.open();
}
@@ -3798,6 +3807,11 @@ public class LettuceConnection extends AbstractRedisConnection {
List<byte[]> values = failsafeReadScanValues(valueScanCursor.getValues(), null);
return new ScanIteration<byte[]>(Long.valueOf(nextCursorId), values);
}
protected void doClose() {
LettuceConnection.this.close();
}
}.open();
}
@@ -3839,6 +3853,11 @@ public class LettuceConnection extends AbstractRedisConnection {
List<Tuple> values = failsafeReadScanValues(result, LettuceConverters.scoredValuesToTupleList());
return new ScanIteration<Tuple>(Long.valueOf(nextCursorId), values);
}
protected void doClose() {
LettuceConnection.this.close();
}
}.open();
}

View File

@@ -235,7 +235,7 @@ class DefaultHashOperations<K, HK, HV> extends AbstractOperations<K, Object> imp
public Cursor<Entry<HK, HV>> scan(K key, final ScanOptions options) {
final byte[] rawKey = rawKey(key);
return execute(new RedisCallback<Cursor<Map.Entry<HK, HV>>>() {
return template.executeWithStickyConnection(new RedisCallback<Cursor<Map.Entry<HK, HV>>>() {
@Override
public Cursor<Entry<HK, HV>> doInRedis(RedisConnection connection) throws DataAccessException {
@@ -268,7 +268,7 @@ class DefaultHashOperations<K, HK, HV> extends AbstractOperations<K, Object> imp
});
}
}, true);
});
}
}

View File

@@ -255,7 +255,7 @@ class DefaultSetOperations<K, V> extends AbstractOperations<K, V> implements Set
public Cursor<V> scan(K key, final ScanOptions options) {
final byte[] rawKey = rawKey(key);
return execute(new RedisCallback<Cursor<V>>() {
return template.executeWithStickyConnection(new RedisCallback<Cursor<V>>() {
@Override
public Cursor<V> doInRedis(RedisConnection connection) throws DataAccessException {
@@ -267,7 +267,7 @@ class DefaultSetOperations<K, V> extends AbstractOperations<K, V> implements Set
}
});
}
}, true);
});
}
}

View File

@@ -409,13 +409,13 @@ class DefaultZSetOperations<K, V> extends AbstractOperations<K, V> implements ZS
public Cursor<TypedTuple<V>> scan(K key, final ScanOptions options) {
final byte[] rawKey = rawKey(key);
Cursor<Tuple> cursor = execute(new RedisCallback<Cursor<Tuple>>() {
Cursor<Tuple> cursor = template.executeWithStickyConnection(new RedisCallback<Cursor<Tuple>>() {
@Override
public Cursor<Tuple> doInRedis(RedisConnection connection) throws DataAccessException {
return connection.zScan(rawKey, options);
}
}, true);
});
return new ConvertingCursor<Tuple, TypedTuple<V>>(cursor, new Converter<Tuple, TypedTuple<V>>() {

View File

@@ -150,7 +150,8 @@ public interface HashOperations<H, HK, HV> {
RedisOperations<H, ?> getOperations();
/**
* Use a {@link Cursor} to iterate over entries in hash at {@code key}.
* Use a {@link Cursor} to iterate over entries in hash at {@code key}. <br />
* <strong>Important:</strong> Call {@link Cursor#close()} when done to avoid resource leak.
*
* @since 1.4
* @param key must not be {@literal null}.

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.redis.core;
import java.io.Closeable;
import java.util.Collection;
import java.util.Date;
import java.util.List;
@@ -22,6 +23,7 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.query.SortQuery;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.core.types.RedisClientInfo;
@@ -129,6 +131,16 @@ public interface RedisOperations<K, V> {
<T> T execute(RedisScript<T> script, RedisSerializer<?> argsSerializer, RedisSerializer<T> resultSerializer,
List<K> keys, Object... args);
/**
* Allocates and binds a new {@link RedisConnection} to the actual return type of the method. It is up to the caller
* to free resources after use.
*
* @param callback must not be {@literal null}.
* @return
* @since 1.8
*/
<T extends Closeable> T executeWithStickyConnection(RedisCallback<T> callback);
Boolean hasKey(K key);
void delete(K key);

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.redis.core;
import java.io.Closeable;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
@@ -304,6 +305,23 @@ public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperation
return scriptExecutor.execute(script, argsSerializer, resultSerializer, keys, args);
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.core.RedisOperations#executeWithStickyConnection(org.springframework.data.redis.core.RedisCallback)
*/
public <T extends Closeable> T executeWithStickyConnection(RedisCallback<T> callback) {
Assert.isTrue(initialized, "template not initialized; call afterPropertiesSet() before using it");
Assert.notNull(callback, "Callback object must not be null");
RedisConnectionFactory factory = getConnectionFactory();
RedisConnection connection = preProcessConnection(RedisConnectionUtils.doGetConnection(factory, true, false, false),
false);
return callback.doInRedis(connection);
}
private Object executeSession(SessionCallback<?> session) {
return session.execute(this);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011-2014 the original author or authors.
* Copyright 2011-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -75,8 +75,9 @@ public interface SetOperations<K, V> {
RedisOperations<K, V> getOperations();
/**
* Iterate over elements in set at {@code key}.
*
* Iterate over elements in set at {@code key}. <br />
* <strong>Important:</strong> Call {@link Cursor#close()} when done to avoid resource leak.
*
* @since 1.4
* @param key
* @param options

View File

@@ -136,6 +136,9 @@ public interface ZSetOperations<K, V> {
RedisOperations<K, V> getOperations();
/**
* Iterate over elements in zset at {@code key}. <br />
* <strong>Important:</strong> Call {@link Cursor#close()} when done to avoid resource leak.
*
* @since 1.4
* @param key
* @param options