464839 Add limit to MongoSessionIdManager purge queries

This commit is contained in:
Jan Bartel 2015-05-15 12:17:17 +10:00
parent c2b74fe997
commit 8e2011d102
3 changed files with 250 additions and 83 deletions

View File

@ -34,6 +34,7 @@ import org.eclipse.jetty.server.SessionManager;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.session.AbstractSessionIdManager;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.util.ConcurrentHashSet;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
@ -109,10 +110,13 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
/**
* the collection of session ids known to this manager
*
* TODO consider if this ought to be concurrent or not
*/
protected final Set<String> _sessionsIds = new HashSet<String>();
protected final Set<String> _sessionsIds = new ConcurrentHashSet<>();
/**
* The maximum number of items to return from a purge query.
*/
private int _purgeLimit = 0;
/**
@ -182,6 +186,12 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
BasicDBObjectBuilder.start().add("id",1).add("version",1).get(),
BasicDBObjectBuilder.start().add("unique",true).add("sparse",false).get());
// index our accessed and valid fields so that purges are faster, note that the "valid" field is first
// so that we can take advantage of index prefixes
// http://docs.mongodb.org/manual/core/index-compound/#compound-index-prefix
_sessions.ensureIndex(
BasicDBObjectBuilder.start().add(MongoSessionManager.__VALID, 1).add(MongoSessionManager.__ACCESSED, 1).get(),
BasicDBObjectBuilder.start().add("sparse", false).add("background", true).get());
}
/* ------------------------------------------------------------ */
@ -194,28 +204,25 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
{
long now = System.currentTimeMillis();
__log.debug("SessionIdManager:scavenge:at {}", now);
synchronized (_sessionsIds)
{
/*
* run a query returning results that:
* - are in the known list of sessionIds
* - the expiry time has passed
*
* we limit the query to return just the __ID so we are not sucking back full sessions
*/
BasicDBObject query = new BasicDBObject();
query.put(MongoSessionManager.__ID,new BasicDBObject("$in", _sessionsIds ));
query.put(MongoSessionManager.__EXPIRY, new BasicDBObject("$gt", 0));
query.put(MongoSessionManager.__EXPIRY, new BasicDBObject("$lt", now));
/*
* run a query returning results that:
* - are in the known list of sessionIds
* - the expiry time has passed
*
* we limit the query to return just the __ID so we are not sucking back full sessions
*/
BasicDBObject query = new BasicDBObject();
query.put(MongoSessionManager.__ID,new BasicDBObject("$in", _sessionsIds ));
query.put(MongoSessionManager.__EXPIRY, new BasicDBObject("$gt", 0));
query.put(MongoSessionManager.__EXPIRY, new BasicDBObject("$lt", now));
DBCursor checkSessions = _sessions.find(query, new BasicDBObject(MongoSessionManager.__ID, 1));
DBCursor checkSessions = _sessions.find(query, new BasicDBObject(MongoSessionManager.__ID, 1));
for ( DBObject session : checkSessions )
{
__log.debug("SessionIdManager:scavenge: expiring session {}", (String)session.get(MongoSessionManager.__ID));
expireAll((String)session.get(MongoSessionManager.__ID));
}
for ( DBObject session : checkSessions )
{
__log.debug("SessionIdManager:scavenge: expiring session {}", (String)session.get(MongoSessionManager.__ID));
expireAll((String)session.get(MongoSessionManager.__ID));
}
}
@ -262,11 +269,16 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
__log.debug("PURGING");
BasicDBObject invalidQuery = new BasicDBObject();
invalidQuery.put(MongoSessionManager.__ACCESSED, new BasicDBObject("$lt",System.currentTimeMillis() - _purgeInvalidAge));
invalidQuery.put(MongoSessionManager.__VALID, false);
invalidQuery.put(MongoSessionManager.__ACCESSED, new BasicDBObject("$lt",System.currentTimeMillis() - _purgeInvalidAge));
DBCursor oldSessions = _sessions.find(invalidQuery, new BasicDBObject(MongoSessionManager.__ID, 1));
if (_purgeLimit > 0)
{
oldSessions.limit(_purgeLimit);
}
for (DBObject session : oldSessions)
{
String id = (String)session.get("id");
@ -280,11 +292,16 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
{
BasicDBObject validQuery = new BasicDBObject();
validQuery.put(MongoSessionManager.__ACCESSED,new BasicDBObject("$lt",System.currentTimeMillis() - _purgeValidAge));
validQuery.put(MongoSessionManager.__VALID, true);
validQuery.put(MongoSessionManager.__ACCESSED,new BasicDBObject("$lt",System.currentTimeMillis() - _purgeValidAge));
oldSessions = _sessions.find(validQuery,new BasicDBObject(MongoSessionManager.__ID,1));
if (_purgeLimit > 0)
{
oldSessions.limit(_purgeLimit);
}
for (DBObject session : oldSessions)
{
String id = (String)session.get(MongoSessionManager.__ID);
@ -356,6 +373,24 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
_scavengePeriod = TimeUnit.SECONDS.toMillis(scavengePeriod);
}
/* ------------------------------------------------------------ */
/**
* The maximum number of items to return from a purge query. If <= 0 there is no limit. Defaults to 0
*/
public void setPurgeLimit(int purgeLimit)
{
_purgeLimit = purgeLimit;
}
/* ------------------------------------------------------------ */
public int getPurgeLimit()
{
return _purgeLimit;
}
/* ------------------------------------------------------------ */
public void setPurgeDelay(long purgeDelay)
{
@ -522,10 +557,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
__log.debug("MongoSessionIdManager:addSession {}", session.getId());
synchronized (_sessionsIds)
{
_sessionsIds.add(session.getId());
}
_sessionsIds.add(session.getId());
}
@ -538,10 +570,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
return;
}
synchronized (_sessionsIds)
{
_sessionsIds.remove(session.getId());
}
_sessionsIds.remove(session.getId());
}
/* ------------------------------------------------------------ */
@ -553,28 +582,26 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
@Override
public void invalidateAll(String sessionId)
{
synchronized (_sessionsIds)
_sessionsIds.remove(sessionId);
//tell all contexts that may have a session object with this id to
//get rid of them
Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
for (int i=0; contexts!=null && i<contexts.length; i++)
{
_sessionsIds.remove(sessionId);
//tell all contexts that may have a session object with this id to
//get rid of them
Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
for (int i=0; contexts!=null && i<contexts.length; i++)
SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
if (sessionHandler != null)
{
SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
if (sessionHandler != null)
{
SessionManager manager = sessionHandler.getSessionManager();
SessionManager manager = sessionHandler.getSessionManager();
if (manager != null && manager instanceof MongoSessionManager)
{
((MongoSessionManager)manager).invalidateSession(sessionId);
}
if (manager != null && manager instanceof MongoSessionManager)
{
((MongoSessionManager)manager).invalidateSession(sessionId);
}
}
}
}
}
}
/* ------------------------------------------------------------ */
/**
@ -584,25 +611,22 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
*/
public void expireAll (String sessionId)
{
synchronized (_sessionsIds)
_sessionsIds.remove(sessionId);
//tell all contexts that may have a session object with this id to
//get rid of them
Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
for (int i=0; contexts!=null && i<contexts.length; i++)
{
_sessionsIds.remove(sessionId);
//tell all contexts that may have a session object with this id to
//get rid of them
Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
for (int i=0; contexts!=null && i<contexts.length; i++)
SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
if (sessionHandler != null)
{
SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
if (sessionHandler != null)
{
SessionManager manager = sessionHandler.getSessionManager();
SessionManager manager = sessionHandler.getSessionManager();
if (manager != null && manager instanceof MongoSessionManager)
{
((MongoSessionManager)manager).expire(sessionId);
}
if (manager != null && manager instanceof MongoSessionManager)
{
((MongoSessionManager)manager).expire(sessionId);
}
}
}
@ -615,24 +639,21 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
//generate a new id
String newClusterId = newSessionId(request.hashCode());
synchronized (_sessionsIds)
_sessionsIds.remove(oldClusterId);//remove the old one from the list
_sessionsIds.add(newClusterId); //add in the new session id to the list
//tell all contexts to update the id
Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
for (int i=0; contexts!=null && i<contexts.length; i++)
{
_sessionsIds.remove(oldClusterId);//remove the old one from the list
_sessionsIds.add(newClusterId); //add in the new session id to the list
//tell all contexts to update the id
Handler[] contexts = _server.getChildHandlersByClass(ContextHandler.class);
for (int i=0; contexts!=null && i<contexts.length; i++)
SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
if (sessionHandler != null)
{
SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
if (sessionHandler != null)
{
SessionManager manager = sessionHandler.getSessionManager();
SessionManager manager = sessionHandler.getSessionManager();
if (manager != null && manager instanceof MongoSessionManager)
{
((MongoSessionManager)manager).renewSessionId(oldClusterId, oldNodeId, newClusterId, getNodeId(newClusterId, request));
}
if (manager != null && manager instanceof MongoSessionManager)
{
((MongoSessionManager)manager).renewSessionId(oldClusterId, oldNodeId, newClusterId, getNodeId(newClusterId, request));
}
}
}

View File

@ -73,7 +73,7 @@ public class PurgeInvalidSessionTest
MongoTestServer server = createServer(0, 1, 0);
ServletContextHandler context = server.addContext(contextPath);
context.addServlet(TestServlet.class, servletMapping);
MongoSessionManager sessionManager = (MongoSessionManager)context.getSessionHandler().getSessionManager();
MongoSessionIdManager idManager = (MongoSessionIdManager)server.getServer().getSessionIdManager();
idManager.setPurge(true);
@ -124,8 +124,83 @@ public class PurgeInvalidSessionTest
}
}
@Test
public void testPurgeInvalidSessionsWithLimit() throws Exception
{
String contextPath = "";
String servletMapping = "/server";
long purgeInvalidAge = 1000; //1 sec
int purgeLimit = 5; // only purge 5 sessions for each purge run
//ensure scavenging is turned off so the purger gets a chance to find the session
MongoTestServer server = createServer(0, 1, 0);
ServletContextHandler context = server.addContext(contextPath);
context.addServlet(TestServlet.class, servletMapping);
// disable purging so we can call it manually below
MongoSessionManager sessionManager = (MongoSessionManager)context.getSessionHandler().getSessionManager();
MongoSessionIdManager idManager = (MongoSessionIdManager)server.getServer().getSessionIdManager();
idManager.setPurge(false);
idManager.setPurgeLimit(purgeLimit);
idManager.setPurgeInvalidAge(purgeInvalidAge);
// don't purge valid sessions
idManager.setPurgeValidAge(0);
server.start();
int port=server.getPort();
try
{
// cleanup any previous sessions that are invalid so that we are starting fresh
idManager.purgeFully();
long sessionCountAtTestStart = sessionManager.getSessionStoreCount();
HttpClient client = new HttpClient();
client.start();
try
{
// create double the purge limit of sessions, and make them all invalid
for (int i = 0; i < purgeLimit * 2; i++)
{
ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=create");
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
String sessionCookie = response.getHeaders().get("Set-Cookie");
assertTrue(sessionCookie != null);
// Mangle the cookie, replacing Path with $Path, etc.
sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
Request request = client.newRequest("http://localhost:" + port + contextPath + servletMapping + "?action=invalidate");
request.header("Cookie", sessionCookie);
response = request.send();
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
}
// sleep for our invalid age period so that the purge below does something
Thread.sleep(purgeInvalidAge * 2);
// validate that we have the right number of sessions before we purge
assertEquals("Expected to find right number of sessions before purge", sessionCountAtTestStart + (purgeLimit * 2), sessionManager.getSessionStoreCount());
// run our purge we should still have items in the DB
idManager.purge();
assertEquals("Expected to find sessions remaining in db after purge run with limit set",
sessionCountAtTestStart + purgeLimit, sessionManager.getSessionStoreCount());
}
finally
{
client.stop();
}
}
finally
{
server.stop();
}
}
public static class TestServlet extends HttpServlet
{
DBCollection _sessions;
@ -163,6 +238,8 @@ public class PurgeInvalidSessionTest
dbSession = _sessions.findOne(new BasicDBObject("id", id));
assertNotNull(dbSession);
assertTrue(dbSession.containsField(MongoSessionManager.__INVALIDATED));
assertTrue(dbSession.containsField(MongoSessionManager.__VALID));
assertTrue(dbSession.get(MongoSessionManager.__VALID).equals(false));
}
else if ("test".equals(action))
{

View File

@ -123,6 +123,75 @@ public class PurgeValidSessionTest
}
@Test
public void testPurgeValidSessionWithPurgeLimitSet() throws Exception
{
String contextPath = "";
String servletMapping = "/server";
long purgeDelay = 1000; //1 sec
long purgeValidAge = 1000; // 1 sec
int purgeLimit = 5; // only purge 5 items in each purge run
//ensure scavenging is turned off so the purger gets a chance to find the session
MongoTestServer server = createServer(0, 1, 0);
ServletContextHandler context = server.addContext(contextPath);
context.addServlet(TestServlet.class, servletMapping);
MongoSessionManager sessionManager = (MongoSessionManager)context.getSessionHandler().getSessionManager();
MongoSessionIdManager idManager = (MongoSessionIdManager)server.getServer().getSessionIdManager();
// disable purging we will run it manually below
idManager.setPurge(false);
idManager.setPurgeLimit(purgeLimit);
idManager.setPurgeDelay(purgeDelay);
idManager.setPurgeValidAge(purgeValidAge); //purge valid sessions older than
server.start();
int port=server.getPort();
try
{
// start with no sessions
idManager.purgeFully();
HttpClient client = new HttpClient();
client.start();
try
{
// create double our purgeLmit number of sessions
for (int i = 0; i < purgeLimit * 2; i++)
{
ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=create");
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
String sessionCookie = response.getHeaders().get("Set-Cookie");
assertTrue(sessionCookie != null);
// don't remember our cookies from call to call
client.getCookieStore().removeAll();
}
// delay long enough for purgeValidAge to apply
Thread.sleep(2* purgeValidAge);
// validate that we have the right number of sessions before we purge
assertEquals("Expected to find right number of sessions before purge", purgeLimit * 2, sessionManager.getSessionStoreCount());
// run our purge
idManager.purge();
assertEquals("Expected to find sessions remaining in db after purge run with limit set",
purgeLimit, sessionManager.getSessionStoreCount());
}
finally
{
client.stop();
}
}
finally
{
server.stop();
}
}
public static class TestServlet extends HttpServlet
{
DBCollection _sessions;