Skip to content

Commit b4feb2b

Browse files
committed
Always automatically persist the session on creation. Fixes #29.
1 parent 994c2f8 commit b4feb2b

File tree

2 files changed

+25
-25
lines changed

2 files changed

+25
-25
lines changed

README.markdown

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -125,20 +125,6 @@ Since each situation is different, the manager gives you several options which c
125125
- `SAVE_ON_CHANGE`: every time `session.setAttribute()` or `session.removeAttribute()` is called the session will be saved. __Note:__ This feature cannot detect changes made to objects already stored in a specific session attribute. __Tradeoffs__: This option will degrade performance slightly as any change to the session will save the session synchronously to Redis.
126126
- `ALWAYS_SAVE_AFTER_REQUEST`: force saving after every request, regardless of whether or not the manager has detected changes to the session. This option is particularly useful if you make changes to objects already stored in a specific session attribute. __Tradeoff:__ This option make actually increase the liklihood of race conditions if not all of your requests change the session.
127127

128-
Possible Issues
129-
---------------
130-
131-
There is the possibility of a race condition that would cause seeming invisibility of the session immediately after your web application logs in a user: if the response has finished streaming and the client requests a new page before the valve has been able to complete saving the session into Redis, then the new request will not see the session.
132-
133-
This condition will be detected by the session manager and a java.lang.IllegalStateException with the message `Race condition encountered: attempted to load session[SESSION_ID] which has been created but not yet serialized.` will be thrown.
134-
135-
Normally this should be incredibly unlikely (insert joke about programmers and "this should never happen" statements here) since the connection to save the session into Redis is almost guaranteed to be faster than the latency between a client receiving the response, processing it, and starting a new request.
136-
137-
Possible solutions:
138-
139-
- Enable the "save on change" feature (see the Persistence Policies documentation). Technically this will still exhibit slight race condition behavior, but it eliminates as much possiblity of errors occurring as possible. __Note__: There's a tradeoff here in that if you make several changes to the session attributes over the lifetime of a long-running request while other short requests are making changes, you'll still end up having the long-running request overwrite the changes made by the short requests. Unfortunately there's no way to completely eliminate all possible race conditions here, so you'll have to determine what's necessary for your specific use case.
140-
- If you encounter errors, then you can force save the session early (before sending a response to the client) then you can retrieve the current session, and call `currentSession.manager.save(currentSession, true)` to synchronously eliminate the race condition. Note: this will only work directly if your application has the actual session object directly exposed. Many frameworks (and often even Tomcat) will expose the session in their own wrapper HttpSession implementing class. You may be able to dig through these layers to expose the actual underlying RedisSession instance--if so, then using that instance will allow you to implement the workaround.
141-
142128
Acknowledgements
143129
----------------
144130

src/main/java/com/radiadesign/catalina/session/RedisSessionManager.java

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -377,11 +377,14 @@ This ensures that the save(session) at the end of the request
377377
currentSessionId.set(sessionId);
378378
currentSessionIsPersisted.set(false);
379379

380-
if (null != session && this.getSaveOnChange()) {
380+
if (null != session) {
381381
try {
382-
save(session);
382+
error = saveInternal(jedis, session, true);
383383
} catch (IOException ex) {
384-
log.error("Error saving newly created session (triggered by saveOnChange=true): " + ex.getMessage());
384+
log.error("Error saving newly created session: " + ex.getMessage());
385+
currentSession.set(null);
386+
currentSessionId.set(null);
387+
session = null;
385388
}
386389
}
387390
} finally {
@@ -494,8 +497,6 @@ public RedisSession loadSessionFromRedis(String id) throws IOException {
494497
if (data == null) {
495498
log.trace("Session " + id + " not found in Redis");
496499
session = null;
497-
} else if (Arrays.equals(NULL_SESSION, data)) {
498-
throw new IllegalStateException("Race condition encountered: attempted to load session[" + id + "] which has been created but not yet serialized.");
499500
} else {
500501
log.trace("Deserializing session " + id + " from Redis");
501502
session = (RedisSession)createEmptySession();
@@ -538,10 +539,25 @@ public void save(Session session, boolean forceSave) throws IOException {
538539
Jedis jedis = null;
539540
Boolean error = true;
540541

542+
try {
543+
jedis = acquireConnection();
544+
error = saveInternal(jedis, session, forceSave);
545+
} catch (IOException e) {
546+
throw e;
547+
} finally {
548+
if (jedis != null) {
549+
returnConnection(jedis, error);
550+
}
551+
}
552+
}
553+
554+
protected boolean saveInternal(Jedis jedis, Session session, boolean forceSave) throws IOException {
555+
Boolean error = true;
556+
541557
try {
542558
log.trace("Saving session " + session + " into Redis");
543559

544-
RedisSession redisSession = (RedisSession) session;
560+
RedisSession redisSession = (RedisSession)session;
545561

546562
if (log.isTraceEnabled()) {
547563
log.trace("Session Contents [" + redisSession.getId() + "]:");
@@ -556,8 +572,6 @@ public void save(Session session, boolean forceSave) throws IOException {
556572
redisSession.resetDirtyTracking();
557573
byte[] binaryId = redisSession.getId().getBytes();
558574

559-
jedis = acquireConnection();
560-
561575
Boolean isCurrentSessionPersisted = this.currentSessionIsPersisted.get();
562576
if (forceSave || sessionIsDirty || (isCurrentSessionPersisted == null || !isCurrentSessionPersisted)) {
563577
jedis.set(binaryId, serializer.serializeFrom(redisSession));
@@ -569,14 +583,14 @@ public void save(Session session, boolean forceSave) throws IOException {
569583
jedis.expire(binaryId, getMaxInactiveInterval());
570584

571585
error = false;
586+
587+
return error;
572588
} catch (IOException e) {
573589
log.error(e.getMessage());
574590

575591
throw e;
576592
} finally {
577-
if (jedis != null) {
578-
returnConnection(jedis, error);
579-
}
593+
return error;
580594
}
581595
}
582596

0 commit comments

Comments
 (0)