Skip to content

Commit 4587d19

Browse files
committed
Add optional save-on-change support. Fixes #14.
1 parent 16b3b4b commit 4587d19

File tree

3 files changed

+49
-3
lines changed

3 files changed

+49
-3
lines changed

README.markdown

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@ This condition will be detected by the session manager and a java.lang.IllegalSt
9898

9999
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.
100100

101-
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)` 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.
101+
Possible solutions:
102+
103+
- Enable the "save on change" feature by setting `saveOnChange` to `true` in your manager declaration in Tomcat's context.xml. Using this feature will degrade performance slightly as any change to the session will save the session synchronously to Redis, and technically this will still exhibit slight race condition behavior, but it eliminates as much possiblity of errors occurring as possible.
104+
- 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)` 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.
102105

103106
Acknowledgements
104107
----------------

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

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,16 @@
44
import org.apache.catalina.Manager;
55
import org.apache.catalina.session.StandardSession;
66
import java.util.HashMap;
7+
import java.io.IOException;
8+
9+
import org.apache.juli.logging.Log;
10+
import org.apache.juli.logging.LogFactory;
711

812

913
public class RedisSession extends StandardSession {
14+
15+
private final Log log = LogFactory.getLog(RedisSession.class);
16+
1017
protected static Boolean manualDirtyTrackingSupportEnabled = false;
1118

1219
public static void setManualDirtyTrackingSupportEnabled(Boolean enabled) {
@@ -54,16 +61,34 @@ public void setAttribute(String key, Object value) {
5461
|| oldValue == null && value != null
5562
|| !value.getClass().isInstance(oldValue)
5663
|| !value.equals(oldValue) ) ) {
57-
changedAttributes.put(key, value);
64+
if (this.manager instanceof RedisSessionManager
65+
&& ((RedisSessionManager)this.manager).getSaveOnChange()) {
66+
try {
67+
((RedisSessionManager)this.manager).save(this);
68+
} catch (IOException ex) {
69+
log.error("Error saving session on setAttribute (triggered by saveOnChange=true): " + ex.getMessage());
70+
}
71+
} else {
72+
changedAttributes.put(key, value);
73+
}
5874
}
5975

6076
super.setAttribute(key, value);
6177
}
6278

6379
@Override
6480
public void removeAttribute(String name) {
65-
dirty = true;
6681
super.removeAttribute(name);
82+
if (this.manager instanceof RedisSessionManager
83+
&& ((RedisSessionManager)this.manager).getSaveOnChange()) {
84+
try {
85+
((RedisSessionManager)this.manager).save(this);
86+
} catch (IOException ex) {
87+
log.error("Error saving session on setAttribute (triggered by saveOnChange=true): " + ex.getMessage());
88+
}
89+
} else {
90+
dirty = true;
91+
}
6792
}
6893

6994
@Override

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ public class RedisSessionManager extends ManagerBase implements Lifecycle {
4848

4949
protected String serializationStrategyClass = "com.radiadesign.catalina.session.JavaSerializer";
5050

51+
protected boolean saveOnChange = false;
52+
5153
/**
5254
* The lifecycle event support for this component.
5355
*/
@@ -97,6 +99,14 @@ public void setSerializationStrategyClass(String strategy) {
9799
this.serializationStrategyClass = strategy;
98100
}
99101

102+
public boolean getSaveOnChange() {
103+
return saveOnChange;
104+
}
105+
106+
public void setSaveOnChange(boolean saveOnChange) {
107+
this.saveOnChange = saveOnChange;
108+
}
109+
100110
@Override
101111
public int getRejectedSessions() {
102112
// Essentially do nothing.
@@ -282,6 +292,14 @@ This ensures that the save(session) at the end of the request
282292
currentSession.set(session);
283293
currentSessionId.set(sessionId);
284294
currentSessionIsPersisted.set(false);
295+
296+
if (this.getSaveOnChange()) {
297+
try {
298+
save(session);
299+
} catch (IOException ex) {
300+
log.error("Error saving newly created session (triggered by saveOnChange=true): " + ex.getMessage());
301+
}
302+
}
285303
} finally {
286304
if (jedis != null) {
287305
returnConnection(jedis, error);

0 commit comments

Comments
 (0)