20
20
import java .time .Duration ;
21
21
import java .time .Instant ;
22
22
import java .time .ZoneId ;
23
+ import java .util .Iterator ;
23
24
import java .util .Map ;
24
25
import java .util .concurrent .ConcurrentHashMap ;
26
+ import java .util .concurrent .ConcurrentMap ;
25
27
import java .util .concurrent .atomic .AtomicReference ;
28
+ import java .util .concurrent .locks .ReentrantLock ;
26
29
27
30
import reactor .core .publisher .Mono ;
28
31
40
43
*/
41
44
public class InMemoryWebSessionStore implements WebSessionStore {
42
45
46
+ /** Minimum period between expiration checks */
47
+ private static final Duration EXPIRATION_CHECK_PERIOD = Duration .ofSeconds (60 );
48
+
49
+
43
50
private static final IdGenerator idGenerator = new JdkIdGenerator ();
44
51
45
52
46
53
private Clock clock = Clock .system (ZoneId .of ("GMT" ));
47
54
48
- private final Map <String , InMemoryWebSession > sessions = new ConcurrentHashMap <>();
55
+ private final ConcurrentMap <String , InMemoryWebSession > sessions = new ConcurrentHashMap <>();
56
+
57
+ private volatile Instant nextExpirationCheckTime = Instant .now (this .clock ).plus (EXPIRATION_CHECK_PERIOD );
58
+
59
+ private final ReentrantLock expirationCheckLock = new ReentrantLock ();
49
60
50
61
51
62
/**
@@ -60,6 +71,8 @@ public class InMemoryWebSessionStore implements WebSessionStore {
60
71
public void setClock (Clock clock ) {
61
72
Assert .notNull (clock , "Clock is required" );
62
73
this .clock = clock ;
74
+ // Force a check when clock changes..
75
+ this .nextExpirationCheckTime = Instant .now (this .clock );
63
76
}
64
77
65
78
/**
@@ -77,20 +90,46 @@ public Mono<WebSession> createWebSession() {
77
90
78
91
@ Override
79
92
public Mono <WebSession > retrieveSession (String id ) {
93
+
94
+ Instant currentTime = Instant .now (this .clock );
95
+
96
+ if (!this .sessions .isEmpty () && !currentTime .isBefore (this .nextExpirationCheckTime )) {
97
+ checkExpiredSessions (currentTime );
98
+ }
99
+
80
100
InMemoryWebSession session = this .sessions .get (id );
81
101
if (session == null ) {
82
102
return Mono .empty ();
83
103
}
84
- else if (session .isExpired ()) {
104
+ else if (session .isExpired (currentTime )) {
85
105
this .sessions .remove (id );
86
106
return Mono .empty ();
87
107
}
88
108
else {
89
- session .updateLastAccessTime ();
109
+ session .updateLastAccessTime (currentTime );
90
110
return Mono .just (session );
91
111
}
92
112
}
93
113
114
+ private void checkExpiredSessions (Instant currentTime ) {
115
+ if (this .expirationCheckLock .tryLock ()) {
116
+ try {
117
+ Iterator <InMemoryWebSession > iterator = this .sessions .values ().iterator ();
118
+ while (iterator .hasNext ()) {
119
+ InMemoryWebSession session = iterator .next ();
120
+ if (session .isExpired (currentTime )) {
121
+ iterator .remove ();
122
+ session .invalidate ();
123
+ }
124
+ }
125
+ }
126
+ finally {
127
+ this .nextExpirationCheckTime = currentTime .plus (EXPIRATION_CHECK_PERIOD );
128
+ this .expirationCheckLock .unlock ();
129
+ }
130
+ }
131
+ }
132
+
94
133
@ Override
95
134
public Mono <Void > removeSession (String id ) {
96
135
this .sessions .remove (id );
@@ -101,7 +140,7 @@ public Mono<WebSession> updateLastAccessTime(WebSession webSession) {
101
140
return Mono .fromSupplier (() -> {
102
141
Assert .isInstanceOf (InMemoryWebSession .class , webSession );
103
142
InMemoryWebSession session = (InMemoryWebSession ) webSession ;
104
- session .updateLastAccessTime ();
143
+ session .updateLastAccessTime (Instant . now ( getClock ()) );
105
144
return session ;
106
145
});
107
146
}
@@ -122,7 +161,7 @@ private class InMemoryWebSession implements WebSession {
122
161
private final AtomicReference <State > state = new AtomicReference <>(State .NEW );
123
162
124
163
125
- InMemoryWebSession () {
164
+ public InMemoryWebSession () {
126
165
this .creationTime = Instant .now (getClock ());
127
166
this .lastAccessTime = this .creationTime ;
128
167
}
@@ -201,25 +240,28 @@ public Mono<Void> save() {
201
240
202
241
@ Override
203
242
public boolean isExpired () {
243
+ return isExpired (Instant .now (getClock ()));
244
+ }
245
+
246
+ private boolean isExpired (Instant currentTime ) {
204
247
if (this .state .get ().equals (State .EXPIRED )) {
205
248
return true ;
206
249
}
207
- if (checkExpired ()) {
250
+ if (checkExpired (currentTime )) {
208
251
this .state .set (State .EXPIRED );
209
252
return true ;
210
253
}
211
254
return false ;
212
255
}
213
256
214
- private boolean checkExpired () {
257
+ private boolean checkExpired (Instant currentTime ) {
215
258
return isStarted () && !this .maxIdleTime .isNegative () &&
216
- Instant . now ( getClock ()) .minus (this .maxIdleTime ).isAfter (this .lastAccessTime );
259
+ currentTime .minus (this .maxIdleTime ).isAfter (this .lastAccessTime );
217
260
}
218
261
219
- private void updateLastAccessTime () {
220
- this .lastAccessTime = Instant . now ( getClock ()) ;
262
+ private void updateLastAccessTime (Instant currentTime ) {
263
+ this .lastAccessTime = currentTime ;
221
264
}
222
-
223
265
}
224
266
225
267
private enum State { NEW , STARTED , EXPIRED }
0 commit comments