Skip to content

Commit 5614a7d

Browse files
Fix Reactive Redis Sessions Never Expiring
Prior to this commit, we were using Mono#and(Mono) to execute operations sequentially on Redis. However, the and(...) operator does not guarantee that the operations will run sequentially. This commit changes from Mono#and to Mono#flatMap Closes gh-2464
1 parent 07eae3a commit 5614a7d

File tree

2 files changed

+43
-4
lines changed

2 files changed

+43
-4
lines changed

spring-session-data-redis/src/integration-test/java/org/springframework/session/data/redis/ReactiveRedisSessionRepositoryITests.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,17 @@
1616

1717
package org.springframework.session.data.redis;
1818

19+
import java.time.Duration;
1920
import java.time.Instant;
2021

22+
import org.junit.jupiter.api.BeforeEach;
2123
import org.junit.jupiter.api.Test;
2224
import org.junit.jupiter.api.extension.ExtendWith;
2325
import reactor.core.publisher.Mono;
2426

2527
import org.springframework.beans.factory.annotation.Autowired;
2628
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.data.redis.core.ReactiveHashOperations;
2730
import org.springframework.data.redis.core.ReactiveRedisOperations;
2831
import org.springframework.session.Session;
2932
import org.springframework.session.data.redis.ReactiveRedisSessionRepository.RedisSession;
@@ -35,8 +38,11 @@
3538

3639
import static org.assertj.core.api.Assertions.assertThat;
3740
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
41+
import static org.mockito.ArgumentMatchers.any;
42+
import static org.mockito.ArgumentMatchers.anyString;
3843
import static org.mockito.ArgumentMatchers.endsWith;
3944
import static org.mockito.BDDMockito.given;
45+
import static org.mockito.BDDMockito.willAnswer;
4046
import static org.mockito.Mockito.reset;
4147
import static org.mockito.Mockito.spy;
4248

@@ -53,6 +59,13 @@ class ReactiveRedisSessionRepositoryITests extends AbstractRedisITests {
5359
@Autowired
5460
private ReactiveRedisSessionRepository repository;
5561

62+
private ReactiveRedisOperations<String, Object> sessionRedisOperations;
63+
64+
@BeforeEach
65+
void setup() {
66+
this.sessionRedisOperations = this.repository.getSessionRedisOperations();
67+
}
68+
5669
@Test
5770
void saves() {
5871
RedisSession toSave = this.repository.createSession().block();
@@ -75,7 +88,8 @@ void saves() {
7588
assertThat(this.repository.findById(toSave.getId()).block()).isNull();
7689
}
7790

78-
@Test // gh-1399
91+
@Test
92+
// gh-1399
7993
void saveMultipleTimes() {
8094
RedisSession session = this.repository.createSession().block();
8195
session.setAttribute("attribute1", "value1");
@@ -245,6 +259,30 @@ void saveChangeSessionIdAfterCheckWhenOriginalKeyDoesNotExistsThenIgnoreError()
245259
reset(spyOperations);
246260
}
247261

262+
// gh-2464
263+
@Test
264+
void saveWhenPutAllIsDelayedThenExpireShouldBeSet() {
265+
ReactiveRedisOperations<String, Object> spy = spy(this.sessionRedisOperations);
266+
ReflectionTestUtils.setField(this.repository, "sessionRedisOperations", spy);
267+
ReactiveHashOperations<String, Object, Object> opsForHash = spy(this.sessionRedisOperations.opsForHash());
268+
given(spy.opsForHash()).willReturn(opsForHash);
269+
willAnswer((invocation) -> Mono.delay(Duration.ofSeconds(1)).then((Mono<Void>) invocation.callRealMethod()))
270+
.given(opsForHash).putAll(anyString(), any());
271+
RedisSession toSave = this.repository.createSession().block();
272+
273+
String expectedAttributeName = "a";
274+
String expectedAttributeValue = "b";
275+
276+
toSave.setAttribute(expectedAttributeName, expectedAttributeValue);
277+
this.repository.save(toSave).block();
278+
279+
String id = toSave.getId();
280+
Duration expireDuration = this.sessionRedisOperations.getExpire("spring:session:sessions:" + id).block();
281+
282+
assertThat(expireDuration).isNotEqualTo(Duration.ZERO);
283+
reset(spy);
284+
}
285+
248286
@Configuration
249287
@EnableRedisWebSession
250288
static class Config extends BaseConfig {

spring-session-data-redis/src/main/java/org/springframework/session/data/redis/ReactiveRedisSessionRepository.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,10 +286,11 @@ private Mono<Void> saveDelta() {
286286
setTtl = ReactiveRedisSessionRepository.this.sessionRedisOperations.persist(sessionKey);
287287
}
288288

289-
return update.and(setTtl).and((s) -> {
289+
Mono<Object> clearDelta = Mono.fromDirect((s) -> {
290290
this.delta.clear();
291291
s.onComplete();
292-
}).then();
292+
});
293+
return update.flatMap((unused) -> setTtl).flatMap((unused) -> clearDelta).then();
293294
}
294295

295296
private Mono<Void> saveChangeSessionId() {
@@ -312,7 +313,7 @@ private Mono<Void> saveChangeSessionId() {
312313
String sessionKey = getSessionKey(sessionId);
313314

314315
return ReactiveRedisSessionRepository.this.sessionRedisOperations.rename(originalSessionKey, sessionKey)
315-
.and(replaceSessionId).onErrorResume((ex) -> {
316+
.flatMap((unused) -> Mono.fromDirect(replaceSessionId)).onErrorResume((ex) -> {
316317
String message = NestedExceptionUtils.getMostSpecificCause(ex).getMessage();
317318
return StringUtils.startsWithIgnoreCase(message, "ERR no such key");
318319
}, (ex) -> Mono.empty());

0 commit comments

Comments
 (0)