Skip to content

Using passivation with eviction: HttpServletRequest.getSession() may return null due to race-condition #14196

@mr-mister123

Description

@mr-mister123

Jetty version(s)
9.4.58
but current version should also be affected

Jetty Environment
Sessionhandling

HTTP version
doesn't matter

Java version/vendor (use: java -version)
OpenJDK Runtime Environment (Temurin)(build 1.8.0_422-b05)
OpenJDK 64-Bit Server VM (Temurin)(build 25.422-b05, mixed mode)

but doesn't matter

OS type/version
Linux

Description
There is a race-condition in AbstractSessionCache. This results in HttpServletRequest.getSession(false) returning 'null' although there is a valid Session. This happens only if passivation in combination with evicition is used. The problem is much more likely to happen if isSaveOnInactiveEviction() == true and you have a slow storage-device and 'big' objects in the Session-Attributes (meaning serialization of data to disc will take much time).
If HttpServletRequest.getSession(false) is called, while serialization of session to disc is running, it will wait for serialization to finish but then return null. Expected behavior would be, that the session will be activated again and then returned.
Everything works fine, if getSession is called, after writing to disc has finished.

This happens due to a race between the worker thread accessing AbstractSessionCache.getAndEnter() and the eviction-thread calling AbstractSessionCache.checkInactiveSession(). The eviction-thread will lock the session, then writing the data to disc. Since writing is slow, there is a big chance for the other thread in getAndEnter() to call doComputeIfAbsent and to get the same session-object the eviction-thread is working on, since this will be removed from the map after it was written to disc. The thread calling getAndEnter() will than try to lock this session-object an therefor wait till the otherone has finished. It then checks is isResident() returns true. But this is not the case. Now logmessage 'Non-resident session in cache' is printed and method returns null. this is wrong.
the simplest way to fix it would be, to double-check in such cases.

Using this custom Session-Cache-implementation fixes this problem instantly:

SessionCache cache = new DefaultSessionCache(handler) {
			
	protected Session getAndEnter(String id, boolean enter) throws Exception {
		Session session = super.getAndEnter(id, enter);
		if (session == null) {
			// possibly stale session detected.
			// try again:
			session = super.getAndEnter(id, enter);
		}
		return session;
	}
	
};

Please be aware, that just modifying checkInactiveSession and removing the session from the map before writing to disc wont fix the problem - it just makes it more unlikely to happen.

How to reproduce?

  1. write a simple Servlet that (when called) calls getSession(true)
  2. if a session was created, put a serializable object into the attributes. you can implement a custom private void writeObject(java.io.ObjectOutputStream out) throws IOException - Method with an integrated Thread.sleep() to get a fool-safe result ;-)
  3. don't forget to add some log-messages
  4. setup your jetty using passivation and eviction. enable SaveOnInactiveEviction
  5. startup and call your servlet through a webbrowser
  6. wait for your log-message, that serialization is in progress
  7. while eviction-thread is sleeping at Thread.sleep() refresh your browser.
  8. getSession() will return null although the session is just evicted, not timed out...

i only tried with jetty 9.4.58 but source of current jetty 12 doesn't seem to differ in this part...

Thanks and greetz,
Karsten

Metadata

Metadata

Assignees

Labels

BugFor general bugs on Jetty side

Type

No type

Projects

Status

🏗 In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions