From 1bc3cfd35e5a9a6b3a188f4a58471900c69ab45f Mon Sep 17 00:00:00 2001 From: Konstantin Yakimov Date: Mon, 12 Jan 2015 17:06:54 +0300 Subject: [PATCH] INT-3604: Add transactionsfor `RedisLockRegistry` JIRA: https://jira.spring.io/browse/INT-3604 There is no test-case to cover the issue, because it isn't so easy to emulate the network glitch between `setIfAbsent` and `expire` --- .../redis/util/RedisLockRegistry.java | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/spring-integration-redis/src/main/java/org/springframework/integration/redis/util/RedisLockRegistry.java b/spring-integration-redis/src/main/java/org/springframework/integration/redis/util/RedisLockRegistry.java index 1996e7665f..b46f21c1fa 100644 --- a/spring-integration-redis/src/main/java/org/springframework/integration/redis/util/RedisLockRegistry.java +++ b/spring-integration-redis/src/main/java/org/springframework/integration/redis/util/RedisLockRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2015 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. @@ -37,8 +37,10 @@ import org.apache.commons.logging.LogFactory; 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.RedisTemplate; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.SessionCallback; +import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import org.springframework.data.redis.serializer.StringRedisSerializer; @@ -315,16 +317,39 @@ public final class RedisLockRegistry implements LockRegistry { */ this.lockedAt = System.currentTimeMillis(); this.threadName = currentThread.getName(); - Boolean success = RedisLockRegistry.this.redisTemplate.boundValueOps( - constructLockKey()).setIfAbsent(this); + + Boolean success = RedisLockRegistry.this.redisTemplate.execute(new SessionCallback() { + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Override + public Boolean execute(RedisOperations ops) throws DataAccessException { + String key = constructLockKey(); + + ops.watch(key); //monitor key + + ops.multi(); //transaction start + + //can't rely on operations result inside transaction, execution is delayed till `exec()` + ops.opsForValue().setIfAbsent(key, RedisLock.this); + + //set expire on key if exists + ops.expire(key, RedisLockRegistry.this.expireAfter, TimeUnit.MILLISECONDS); + + //exec will contain all operations result or null - if execution has been aborted due to 'watch' + List result = ops.exec(); + + //check 'setIfAbsent' result (first in list) + return (result != null) && (!result.isEmpty()) && (Boolean.TRUE.equals(result.get(0))); + } + + }); + if (!success) { this.lockedAt = 0; this.threadName = null; } else { this.thread = currentThread; - RedisLockRegistry.this.redisTemplate.expire(constructLockKey(), - RedisLockRegistry.this.expireAfter, TimeUnit.MILLISECONDS); if (logger.isDebugEnabled()) { logger.debug("New lock; " + this.toString()); }