INT-4126: Fix Redis lock to use SET NX EX
JIRA: https://jira.spring.io/browse/INT-4126 Looks like `WATCH` doesn't provide good isolation when the call is performed concurrently at the same time from different Threads. * Change locking algorithm as it is recommended by Redis `SET` command: http://redis.io/commands/set: ``` SET resource-name anystring NX EX max-lock-time ``` * Add `@Repeat(10)` to the `AggregatorWithRedisLocksTests#testDistributedAggregator()` since that helped to reproduce the issue **Cherry-pick to 4.3.x & 4.2.x**
This commit is contained in:
@@ -41,9 +41,8 @@ import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisCallback;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.SessionCallback;
|
||||
import org.springframework.data.redis.core.TimeoutUtils;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.SerializationException;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
@@ -420,32 +419,32 @@ public final class RedisLockRegistry implements LockRegistry {
|
||||
|
||||
Boolean success = false;
|
||||
try {
|
||||
success = RedisLockRegistry.this.redisTemplate.execute(new SessionCallback<Boolean>() {
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
success = RedisLockRegistry.this.redisTemplate.execute(new RedisCallback<Boolean>() {
|
||||
|
||||
@Override
|
||||
public Boolean execute(RedisOperations ops) throws DataAccessException {
|
||||
String key = constructLockKey();
|
||||
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
|
||||
|
||||
ops.watch(key); //monitor key
|
||||
/*
|
||||
Perform Redis command 'SET resource-name anystring NX EX max-lock-time' directly.
|
||||
As it is recommended by Redis: http://redis.io/commands/set.
|
||||
This command isn't supported directly by RedisTemplate.
|
||||
*/
|
||||
long expireAfter = TimeoutUtils.toSeconds(RedisLockRegistry.this.expireAfter,
|
||||
TimeUnit.MILLISECONDS);
|
||||
RedisSerializer<String> serializer = RedisLockRegistry.this.redisTemplate.getStringSerializer();
|
||||
byte[][] actualArgs = new byte[][] {
|
||||
serializer.serialize(constructLockKey()),
|
||||
RedisLockRegistry.this.lockSerializer.serialize(RedisLock.this),
|
||||
serializer.serialize("NX"),
|
||||
serializer.serialize("EX"),
|
||||
serializer.serialize(String.valueOf(expireAfter))
|
||||
};
|
||||
|
||||
if (ops.opsForValue().get(key) != null) {
|
||||
ops.unwatch(); //key already exists, stop monitoring
|
||||
return false;
|
||||
}
|
||||
|
||||
ops.multi(); //transaction start
|
||||
|
||||
//set the value and expire
|
||||
ops.opsForValue()
|
||||
.set(key, RedisLock.this, RedisLockRegistry.this.expireAfter, TimeUnit.MILLISECONDS);
|
||||
|
||||
//exec will contain all operations result or null - if execution has been aborted due to 'watch'
|
||||
return ops.exec() != null;
|
||||
return connection.execute("SET", actualArgs) != null;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
finally {
|
||||
|
||||
|
||||
@@ -46,11 +46,13 @@ import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.annotation.Repeat;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
* @since 4.0
|
||||
*
|
||||
*/
|
||||
@@ -125,6 +127,7 @@ public class AggregatorWithRedisLocksTests extends RedisAvailableTests {
|
||||
|
||||
@Test
|
||||
@RedisAvailable
|
||||
@Repeat(10)
|
||||
public void testDistributedAggregator() throws Exception {
|
||||
this.releaseStrategy.reset(1);
|
||||
Executors.newSingleThreadExecutor().execute(asyncSend("foo", 1, 1));
|
||||
@@ -223,5 +226,7 @@ public class AggregatorWithRedisLocksTests extends RedisAvailableTests {
|
||||
this.callers = new AtomicInteger();
|
||||
this.maxCallers = new AtomicInteger();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user