464839 Add limit to MongoSessionIdManager purge queries
This commit is contained in:
parent
c2b74fe997
commit
8e2011d102
|
@ -34,6 +34,7 @@ import org.eclipse.jetty.server.SessionManager;
|
||||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||||
import org.eclipse.jetty.server.session.AbstractSessionIdManager;
|
import org.eclipse.jetty.server.session.AbstractSessionIdManager;
|
||||||
import org.eclipse.jetty.server.session.SessionHandler;
|
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.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
|
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
|
* 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("id",1).add("version",1).get(),
|
||||||
BasicDBObjectBuilder.start().add("unique",true).add("sparse",false).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,8 +204,6 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
|
||||||
{
|
{
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
__log.debug("SessionIdManager:scavenge:at {}", now);
|
__log.debug("SessionIdManager:scavenge:at {}", now);
|
||||||
synchronized (_sessionsIds)
|
|
||||||
{
|
|
||||||
/*
|
/*
|
||||||
* run a query returning results that:
|
* run a query returning results that:
|
||||||
* - are in the known list of sessionIds
|
* - are in the known list of sessionIds
|
||||||
|
@ -217,7 +225,6 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
|
||||||
expireAll((String)session.get(MongoSessionManager.__ID));
|
expireAll((String)session.get(MongoSessionManager.__ID));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
|
@ -262,11 +269,16 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
|
||||||
__log.debug("PURGING");
|
__log.debug("PURGING");
|
||||||
BasicDBObject invalidQuery = new BasicDBObject();
|
BasicDBObject invalidQuery = new BasicDBObject();
|
||||||
|
|
||||||
invalidQuery.put(MongoSessionManager.__ACCESSED, new BasicDBObject("$lt",System.currentTimeMillis() - _purgeInvalidAge));
|
|
||||||
invalidQuery.put(MongoSessionManager.__VALID, false);
|
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));
|
DBCursor oldSessions = _sessions.find(invalidQuery, new BasicDBObject(MongoSessionManager.__ID, 1));
|
||||||
|
|
||||||
|
if (_purgeLimit > 0)
|
||||||
|
{
|
||||||
|
oldSessions.limit(_purgeLimit);
|
||||||
|
}
|
||||||
|
|
||||||
for (DBObject session : oldSessions)
|
for (DBObject session : oldSessions)
|
||||||
{
|
{
|
||||||
String id = (String)session.get("id");
|
String id = (String)session.get("id");
|
||||||
|
@ -280,11 +292,16 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
|
||||||
{
|
{
|
||||||
BasicDBObject validQuery = new BasicDBObject();
|
BasicDBObject validQuery = new BasicDBObject();
|
||||||
|
|
||||||
validQuery.put(MongoSessionManager.__ACCESSED,new BasicDBObject("$lt",System.currentTimeMillis() - _purgeValidAge));
|
|
||||||
validQuery.put(MongoSessionManager.__VALID, true);
|
validQuery.put(MongoSessionManager.__VALID, true);
|
||||||
|
validQuery.put(MongoSessionManager.__ACCESSED,new BasicDBObject("$lt",System.currentTimeMillis() - _purgeValidAge));
|
||||||
|
|
||||||
oldSessions = _sessions.find(validQuery,new BasicDBObject(MongoSessionManager.__ID,1));
|
oldSessions = _sessions.find(validQuery,new BasicDBObject(MongoSessionManager.__ID,1));
|
||||||
|
|
||||||
|
if (_purgeLimit > 0)
|
||||||
|
{
|
||||||
|
oldSessions.limit(_purgeLimit);
|
||||||
|
}
|
||||||
|
|
||||||
for (DBObject session : oldSessions)
|
for (DBObject session : oldSessions)
|
||||||
{
|
{
|
||||||
String id = (String)session.get(MongoSessionManager.__ID);
|
String id = (String)session.get(MongoSessionManager.__ID);
|
||||||
|
@ -356,6 +373,24 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
|
||||||
_scavengePeriod = TimeUnit.SECONDS.toMillis(scavengePeriod);
|
_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)
|
public void setPurgeDelay(long purgeDelay)
|
||||||
{
|
{
|
||||||
|
@ -522,10 +557,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
|
||||||
|
|
||||||
__log.debug("MongoSessionIdManager:addSession {}", session.getId());
|
__log.debug("MongoSessionIdManager:addSession {}", session.getId());
|
||||||
|
|
||||||
synchronized (_sessionsIds)
|
|
||||||
{
|
|
||||||
_sessionsIds.add(session.getId());
|
_sessionsIds.add(session.getId());
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -538,11 +570,8 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (_sessionsIds)
|
|
||||||
{
|
|
||||||
_sessionsIds.remove(session.getId());
|
_sessionsIds.remove(session.getId());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/** Remove the session id from the list of in-use sessions.
|
/** Remove the session id from the list of in-use sessions.
|
||||||
|
@ -552,8 +581,6 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void invalidateAll(String sessionId)
|
public void invalidateAll(String sessionId)
|
||||||
{
|
|
||||||
synchronized (_sessionsIds)
|
|
||||||
{
|
{
|
||||||
_sessionsIds.remove(sessionId);
|
_sessionsIds.remove(sessionId);
|
||||||
|
|
||||||
|
@ -574,7 +601,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
|
@ -583,8 +610,6 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
|
||||||
* @param sessionId the session id
|
* @param sessionId the session id
|
||||||
*/
|
*/
|
||||||
public void expireAll (String sessionId)
|
public void expireAll (String sessionId)
|
||||||
{
|
|
||||||
synchronized (_sessionsIds)
|
|
||||||
{
|
{
|
||||||
_sessionsIds.remove(sessionId);
|
_sessionsIds.remove(sessionId);
|
||||||
|
|
||||||
|
@ -606,7 +631,6 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
@Override
|
@Override
|
||||||
|
@ -615,8 +639,6 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
|
||||||
//generate a new id
|
//generate a new id
|
||||||
String newClusterId = newSessionId(request.hashCode());
|
String newClusterId = newSessionId(request.hashCode());
|
||||||
|
|
||||||
synchronized (_sessionsIds)
|
|
||||||
{
|
|
||||||
_sessionsIds.remove(oldClusterId);//remove the old one from the list
|
_sessionsIds.remove(oldClusterId);//remove the old one from the list
|
||||||
_sessionsIds.add(newClusterId); //add in the new session id to the list
|
_sessionsIds.add(newClusterId); //add in the new session id to the list
|
||||||
|
|
||||||
|
@ -636,6 +658,5 @@ public class MongoSessionIdManager extends AbstractSessionIdManager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,6 +126,81 @@ 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
|
public static class TestServlet extends HttpServlet
|
||||||
{
|
{
|
||||||
DBCollection _sessions;
|
DBCollection _sessions;
|
||||||
|
@ -163,6 +238,8 @@ public class PurgeInvalidSessionTest
|
||||||
dbSession = _sessions.findOne(new BasicDBObject("id", id));
|
dbSession = _sessions.findOne(new BasicDBObject("id", id));
|
||||||
assertNotNull(dbSession);
|
assertNotNull(dbSession);
|
||||||
assertTrue(dbSession.containsField(MongoSessionManager.__INVALIDATED));
|
assertTrue(dbSession.containsField(MongoSessionManager.__INVALIDATED));
|
||||||
|
assertTrue(dbSession.containsField(MongoSessionManager.__VALID));
|
||||||
|
assertTrue(dbSession.get(MongoSessionManager.__VALID).equals(false));
|
||||||
}
|
}
|
||||||
else if ("test".equals(action))
|
else if ("test".equals(action))
|
||||||
{
|
{
|
||||||
|
|
|
@ -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
|
public static class TestServlet extends HttpServlet
|
||||||
{
|
{
|
||||||
DBCollection _sessions;
|
DBCollection _sessions;
|
||||||
|
|
Loading…
Reference in New Issue