MultiRedisLock implements Lock { @Override public String acquire(final List<String> keys, final String storeId, final long expiration) { final var keysWithStoreIdPrefix = keys.stream().map(key -> storeId + ":" + key).collect(Collectors.toList()); final var token = tokenSupplier.get(); if (StringUtils.isEmpty(token)) { throw new IllegalStateException("Cannot lock with empty token"); } final var locked = stringRedisTemplate.execute(lockScript, keysWithStoreIdPrefix, token, String.valueOf(expiration)); log.debug("Tried to acquire lock for keys {} in store {} with token {}. Locked: {}", keys, storeId, token, locked); return locked ? token : null; } MultiRedisLock(final StringRedisTemplate stringRedisTemplate); @Override String acquire(final List<String> keys, final String storeId, final long expiration); @Override boolean release(final List<String> keys, final String storeId, final String token); @Override boolean refresh(final List<String> keys, final String storeId, final String token, final long expiration);  }
@Test public void shouldExpire() throws InterruptedException { lock.acquire(Arrays.asList("1", "2"), "locks", 100); Thread.sleep(100); assertThat(redisTemplate.opsForValue().get("locks:1")).isNull(); assertThat(redisTemplate.opsForValue().get("locks:2")).isNull(); }
@Test public void shouldLockSingleKey() { final var token = lock.acquire(Collections.singletonList("1"), "locks", 1000); assertThat(token).isEqualTo("abc"); assertThat(redisTemplate.opsForValue().get("locks:1")).isEqualTo("abc"); assertThat(redisTemplate.getExpire("locks:1", TimeUnit.MILLISECONDS)).isCloseTo(1000, Offset.offset(100L)); }
@Test public void shouldLockMultipleKeys() { final var token = lock.acquire(Arrays.asList("1", "2"), "locks", 1000); assertThat(token).isEqualTo("abc"); assertThat(redisTemplate.opsForValue().get("locks:1")).isEqualTo("abc"); assertThat(redisTemplate.opsForValue().get("locks:2")).isEqualTo("abc"); assertThat(redisTemplate.getExpire("locks:1", TimeUnit.MILLISECONDS)).isCloseTo(1000, Offset.offset(100L)); assertThat(redisTemplate.getExpire("locks:2", TimeUnit.MILLISECONDS)).isCloseTo(1000, Offset.offset(100L)); }
@Test public void shouldNotLockWhenWholeLockIsTaken() { redisTemplate.opsForValue().set("locks:1", "def"); redisTemplate.opsForValue().set("locks:2", "ghi"); final var token = lock.acquire(Arrays.asList("1", "2"), "locks", 1000); assertThat(token).isNull(); assertThat(redisTemplate.opsForValue().get("locks:1")).isEqualTo("def"); assertThat(redisTemplate.opsForValue().get("locks:2")).isEqualTo("ghi"); }
@Test public void shouldNotLockWhenLockIsPartiallyTaken() { redisTemplate.opsForValue().set("locks:1", "def"); final var token = lock.acquire(Arrays.asList("1", "2"), "locks", 1000); assertThat(token).isNull(); assertThat(redisTemplate.opsForValue().get("locks:1")).isEqualTo("def"); assertThat(redisTemplate.opsForValue().get("locks:2")).isNull(); }