The Problem:
The background task that cleans up sessions can incorrectly remove a
session due to a race condition.
Assume an existing session with the id "1" exists and will expire at
1420656360000. This means our redis store has the following:
spring:session:expirations:1420656360000 -> [1]
spring:session:session:1 -> <session>
Consider the following sequence:
* Thread 1 requests Session 1 and determines it should be forcibly deleted
up at 1420656420000
* Thread 2 requests Session 2 and determines it should be forcibly deleted
one minute later at 1420656480000
* Thread 2 removes Session 1 from 1420656360000, so it will no longer be
forcibly deleted at that time
spring:session:expirations:1420656360000 -> []
spring:session:session:1 -> <session>
* Thread 2 adds Session 1 to 1420656480000
spring:session:expirations:1420656360000 -> []
spring:session:session:1 -> <session>
spring:session:expirations:1420656480000 -> [1]
* Thread 1 removes Session 1 (which was already removed) from 1420656360000 (the original expiration)
spring:session:expirations:1420656360000 -> []
spring:session:session:1 -> <session>
spring:session:expirations:1420656480000 -> [1]
* Thread 1 adds Session 1 to 1420656420000
spring:session:expirations:1420656360000 -> []
spring:session:session:1 -> <session>
spring:session:expirations:1420656480000 -> [1]
spring:session:expirations:1420656420000 -> [1]
Now the session is mapped to be forcibly deleted in two locations.
However, at most it will be cleaned up in one location. This means that the
session will be forcibly deleted even if the session is continued to be
used.
Fixing the Issue:
Instead of deleting the session, we should have the background task access
the key which will only forcibly delete the key if it is expired. This mean
s that a session could at earliest be deleted when the value in the
datastore indicates.
This still means that a session can be deleted too soon since the
incorrect TTL may be set on a key. However, at worst this is the the
longest HTTP request length. Short of using distributed locking there
isn't a good answer to get exact consistency.
Fixes gh-93
Previously there was a possibility that Session to WebSocket mapping was
leaked if keyspace notifications were not enabled in Redis.
To resolve this the RedisHttpSessionConfiguration now ensures that Redis
is configured to enable Keyspace notifications.
Fixes gh-76 gh-81
This both provides two things:
- more meaningful parameter name since it relates to session
- less likely to have a collission due to the _ prefix
gh-49
Add support for SessionDestroyedEvent so that applications can detect
when a Spring Session has expired or explicitly deleted. This will
ensure that we can cleanup resources (i.e. WebSockets) can be cleaned up
when a Session ends.
Fixes gh-60
Redis key expiration has no guarantees of when the expired key is
actually removed. In some instances, it is necessary to clean up resources
as soon as the sessione expires. For example, when an HTTP Session expires
we must ensure that the associated Web Socket sessions are closed.
Sessions are now mapped to their expiration times and a background task
is used to clean up the sessions as they expire.
Fixes gh-59
At times OnCommittedResponseWrapper#onResponseCommitted() can be invoked
multiple times. For example, when flush is performed multiple times. This
means that the session can be written multiple times which is inefficient.
Instead, we should only save on the first update.
Fixes gh-57
Previously the key contained spring-security-sessions in it since the
project was originally going to be a part of Spring Security.
This commit moves it to spring:session:sessions: to better align with
being part of Spring Session
Fixes#46
* Ensure that Maven Source Jars are published
* Ensure that Javadoc is published
* Ensure Maven pom includes required information for central
Fixes gh-36