Is synchronization within an HttpSession feasible?
UPDATE: Solution right after question.
Question:
Usually, synchronization is serializing parallel requests within a JVM, e.g.
private static final Object LOCK = new Object();
public void doSomething() {
...
synchronized(LOCK) {
...
}
...
}
When looking at web applications, some synchronization on "JVM global" scope is maybe becoming a performance bottleneck and synchronization only within the scope of the user's HttpSession would make more sense.
Is the following code a possibility? I doubt that synchronizing on the session object is a good idea but it would be interesting to hear your thoughts.
HttpSession session = getHttpServletRequest().getSession();
synchronized (session) {
...
}
Key Question:
Is it guaranteed that the session object is the same instance for all threads processing requests from the same user?
Summarized answer / solution:
It seems that the session object itself is not always the same as it depends on the implementation of the servlet container (Tomcat, Glassfish, ...) and the getSession()
method might return just a wrapper instance.
So it is recommended to use a custom variable stored in the session to be used as locking object.
Here is my code proposal, feedback is welcome:
somewhere in a Helper Class, e.g. MyHelper
:
private static final Object LOCK = new Object();
public static Object getSessionLock(HttpServletRequest request, String lockName) {
if (lockName == null) lockName = "SESSION_LOCK";
Object result = request.getSession().getAttribute(lockName);
if (result == null) {
// only if there is no session-lock object in the session we apply the global lock
synchronized (LOCK) {
// as it can be that another thread has updated the session-lock object in the meantime, we have to read it again from the session and create it only if it is not there yet!
result = request.getSession().getAttribute(lockName);
if (result == null) {
result = new Object();
request.getSession().setAttribute(lockName, result);
}
}
}
return result;
}
and then you can use it:
Object sessionLock = MyHelper.getSessionLock(getRequest(), null);
synchronized (sessionLock) {
...
}
Any comments on this solution?
I found this nice explanation in spring-mvc JavaDoc for WebUtils.getSessionMutex()
:
In many cases, the HttpSession reference itself is a safe mutex as well, since it will always be the same object reference for the same active logical session. However, this is not guaranteed across different servlet containers; the only 100% safe way is a session mutex.
This method is used as a lock when synchronizeOnSession
flag is set:
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
return handleRequestInternal(request, response);
}
If you look at the implementation of getSessionMutex()
, it actually uses some custom session attribute if present (under org.springframework.web.util.WebUtils.MUTEX
key) or HttpSession
instance if not:
Object mutex = session.getAttribute(SESSION_MUTEX_ATTRIBUTE);
if (mutex == null) {
mutex = session;
}
return mutex;
Back to plain servlet spec - to be 100% sure use custom session attribute rather than HttpSession
object itself.
See also
- http://www.theserverside.com/discussions/thread.tss?thread_id=42912
In general, don't rely on HttpServletRequest.getSession()
returning same object. It's easy for servlet filters to create a wrapper around session for whatever reason. Your code will only see this wrapper, and it will be different object on each request. Put some shared lock into the session itself. (Too bad there is no putIfAbsent
though).
As people already said, sessions can be wrapped by the servlet containers and this generates a problem: the session hashCode() is different between requests, i.e., they are not the same instance and thus can't be synchronized! Many containers allow persist a session. In this cases, in certain time, when session was expired, it is persisted on disk. Even when session is retrieved by deserialization, it is not same object as earlier, because it don't shares same memory address like when was at memory before the serialization process. When session is loaded from disk, it is put into memory for further access, until "maxInactiveInterval" is reached (expires). Summing up: the session could be not the same between many web requests! It will be the same while is in memory. Even if you put an attribute into the session to share lock, it will not work, because it will be serialized as well in the persistence phase.