From 092c53b335c6ca8aa49a20698faf008d0469dbf4 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Thu, 21 Nov 2013 12:11:24 +1100 Subject: [PATCH] 410750 Mongo clustered sessions persisted across stops --- .../org/eclipse/jetty/nosql/NoSqlSession.java | 15 +- .../jetty/nosql/NoSqlSessionManager.java | 139 +++++-- .../nosql/mongodb/MongoSessionIdManager.java | 95 +++-- .../nosql/mongodb/MongoSessionManager.java | 249 ++++++++++--- .../session/AbstractSessionManager.java | 13 +- .../server/session/HashSessionManager.java | 6 +- .../server/session/JDBCSessionManager.java | 349 +++++++++--------- .../server/session/SessionCookieTest.java | 4 +- .../jetty/server/session/JdbcTestServer.java | 36 +- .../SessionInvalidateAndCreateTest.java | 38 ++ ...StopSessionManagerPreserveSessionTest.java | 81 ++++ .../test-mongodb-sessions/pom.xml | 2 +- .../nosql/mongodb/InvalidateSessionTest.java | 54 +++ .../mongodb/LocalSessionScavengingTest.java | 41 ++ .../jetty/nosql/mongodb/MongoTestServer.java | 4 +- .../mongodb/PurgeInvalidSessionTest.java | 18 +- .../nosql/mongodb/SessionExpiryTest.java | 45 +++ .../SessionInvalidateAndCreateTest.java | 41 ++ .../nosql/mongodb/SessionMigrationTest.java | 39 ++ .../StopSessionManagerDeleteSessionTest.java | 164 ++++++++ ...StopSessionManagerPreserveSessionTest.java | 98 +++++ ...bstractSessionInvalidateAndCreateTest.java | 38 +- ...StopSessionManagerPreserveSessionTest.java | 116 ++++++ 23 files changed, 1345 insertions(+), 340 deletions(-) create mode 100644 tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionInvalidateAndCreateTest.java create mode 100644 tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/StopSessionManagerPreserveSessionTest.java create mode 100644 tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/InvalidateSessionTest.java create mode 100644 tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/LocalSessionScavengingTest.java create mode 100644 tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionExpiryTest.java create mode 100644 tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionInvalidateAndCreateTest.java create mode 100644 tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionMigrationTest.java create mode 100644 tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/StopSessionManagerDeleteSessionTest.java create mode 100644 tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/StopSessionManagerPreserveSessionTest.java create mode 100644 tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractStopSessionManagerPreserveSessionTest.java diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSession.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSession.java index 53fb9805aa1..db16bb5bc87 100644 --- a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSession.java +++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSession.java @@ -45,7 +45,6 @@ public class NoSqlSession extends AbstractSession { super(manager, request); _manager=manager; - save(true); _active.incrementAndGet(); } @@ -88,6 +87,14 @@ public class NoSqlSession extends AbstractSession _dirty.add(name); } } + + + + @Override + protected void timeout() throws IllegalStateException + { + super.timeout(); + } /* * a boolean version of the setAttribute method that lets us manage the _dirty set @@ -125,7 +132,7 @@ public class NoSqlSession extends AbstractSession @Override protected boolean access(long time) { - __log.debug("NoSqlSession:access:active "+_active); + __log.debug("NoSqlSession:access:active {} time {}", _active, time); if (_active.incrementAndGet()==1) { long period=_manager.getStalePeriod()*1000L; @@ -169,6 +176,7 @@ public class NoSqlSession extends AbstractSession protected void doInvalidate() throws IllegalStateException { super.doInvalidate(); + //jb why save here? if the session is invalidated it should be removed save(false); } @@ -232,7 +240,4 @@ public class NoSqlSession extends AbstractSession { super.setNodeId(nodeId); } - - - } diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSessionManager.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSessionManager.java index d5ebdd29383..719aaf370cf 100644 --- a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSessionManager.java +++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSessionManager.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.nosql; import java.util.ArrayList; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest; @@ -30,6 +31,12 @@ import org.eclipse.jetty.server.session.AbstractSessionManager; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +/** + * NoSqlSessionManager + * + * Base class for SessionManager implementations using nosql frameworks + * + */ public abstract class NoSqlSessionManager extends AbstractSessionManager implements SessionManager { private final static Logger __log = Log.getLogger("org.eclipse.jetty.server.session"); @@ -40,11 +47,11 @@ public abstract class NoSqlSessionManager extends AbstractSessionManager impleme private int _savePeriod=0; private int _idlePeriod=-1; private boolean _invalidateOnStop; - private boolean _preserveOnStop; + private boolean _preserveOnStop = true; private boolean _saveAllAttributes; /* ------------------------------------------------------------ */ - /* (non-Javadoc) + /** * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStart() */ @Override @@ -59,7 +66,12 @@ public abstract class NoSqlSessionManager extends AbstractSessionManager impleme protected void addSession(AbstractSession session) { if (isRunning()) + { + //add into memory _sessions.put(session.getClusterId(),(NoSqlSession)session); + //add into db + ((NoSqlSession)session).save(true); + } } /* ------------------------------------------------------------ */ @@ -67,15 +79,16 @@ public abstract class NoSqlSessionManager extends AbstractSessionManager impleme public AbstractSession getSession(String idInCluster) { NoSqlSession session = _sessions.get(idInCluster); - - __log.debug("getSession: " + session ); + __log.debug("getSession {} ", session ); if (session==null) { + //session not in this node's memory, load it session=loadSession(idInCluster); if (session!=null) { + //session exists, check another request thread hasn't loaded it too NoSqlSession race=_sessions.putIfAbsent(idInCluster,session); if (race!=null) { @@ -83,54 +96,83 @@ public abstract class NoSqlSessionManager extends AbstractSessionManager impleme session.clearAttributes(); session=race; } + else + __log.debug("session loaded ", idInCluster); + + //check if the session we just loaded has actually expired, maybe while we weren't running + if (getMaxInactiveInterval() > 0 && session.getAccessed() > 0 && ((getMaxInactiveInterval()*1000)+session.getAccessed()) < System.currentTimeMillis()) + { + __log.debug("session expired ", idInCluster); + expire(idInCluster); + session = null; + } } + else + __log.debug("session does not exist {}", idInCluster); } - + return session; } /* ------------------------------------------------------------ */ @Override - protected void invalidateSessions() throws Exception + protected void shutdownSessions() throws Exception { - // Invalidate all sessions to cause unbind events + //If we are stopping, and we're preserving sessions, then we want to + //save all of the sessions (including those that have been added during this method call) + //and then just remove them from memory. + + //If we don't wish to preserve sessions and we're stopping, then we should invalidate + //the session (which may remove it). + long gracefulStopMs = getContextHandler().getServer().getStopTimeout(); + long stopTime = 0; + if (gracefulStopMs > 0) + stopTime = System.nanoTime() + (TimeUnit.NANOSECONDS.convert(gracefulStopMs, TimeUnit.MILLISECONDS)); + ArrayList sessions=new ArrayList(_sessions.values()); - int loop=100; - while (sessions.size()>0 && loop-->0) - { - // If we are called from doStop - if (isStopping()) - { - // Then we only save and remove the session - it is not invalidated. - for (NoSqlSession session : sessions) - { - session.save(false); - if (!_preserveOnStop) { - removeSession(session,false); - } + // loop while there are sessions, and while there is stop time remaining, or if no stop time, just 1 loop + while (sessions.size() > 0 && ((stopTime > 0 && (System.nanoTime() < stopTime)) || (stopTime == 0))) + { + for (NoSqlSession session : sessions) + { + if (isPreserveOnStop()) + { + //we don't want to delete the session, so save the session + //and remove from memory + session.save(false); + _sessions.remove(session.getClusterId()); + } + else + { + //invalidate the session so listeners will be called and also removes the session + session.invalidate(); } } - else - { - for (NoSqlSession session : sessions) - session.invalidate(); - } - // check that no new sessions were created while we were iterating + //check if we should terminate our loop if we're not using the stop timer + if (stopTime == 0) + { + break; + } + // Get any sessions that were added by other requests during processing and go around the loop again sessions=new ArrayList(_sessions.values()); } } + /* ------------------------------------------------------------ */ @Override protected AbstractSession newSession(HttpServletRequest request) { - long created=System.currentTimeMillis(); return new NoSqlSession(this,request); } /* ------------------------------------------------------------ */ + /** Remove the session from the in-memory list for this context. + * Also remove the context sub-document for this session id from the db. + * @see org.eclipse.jetty.server.session.AbstractSessionManager#removeSession(java.lang.String) + */ @Override protected boolean removeSession(String idInCluster) { @@ -147,7 +189,7 @@ public abstract class NoSqlSessionManager extends AbstractSessionManager impleme } catch (Exception e) { - __log.warn("Problem deleting session id=" + idInCluster,e); + __log.warn("Problem deleting session {}", idInCluster,e); } return session != null; @@ -155,31 +197,52 @@ public abstract class NoSqlSessionManager extends AbstractSessionManager impleme } /* ------------------------------------------------------------ */ - protected void invalidateSession( String idInCluster ) + protected void expire( String idInCluster ) { synchronized (this) { - NoSqlSession session = _sessions.remove(idInCluster); + //get the session from memory + NoSqlSession session = _sessions.get(idInCluster); try { + if (session == null) + { + //we need to expire the session with its listeners, so load it + session = loadSession(idInCluster); + } + + if (session != null) + session.timeout(); + } + catch (Exception e) + { + __log.warn("Problem expiring session {}", idInCluster,e); + } + } + } + + + public void invalidateSession (String idInCluster) + { + synchronized (this) + { + NoSqlSession session = _sessions.get(idInCluster); + try + { + __log.debug("invalidating session {}", idInCluster); if (session != null) { - remove(session); + session.invalidate(); } } catch (Exception e) { - __log.warn("Problem deleting session id=" + idInCluster,e); + __log.warn("Problem invalidating session {}", idInCluster,e); } } - - /* - * ought we not go to cluster and mark it invalid? - */ - } - + /* ------------------------------------------------------------ */ /** diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionIdManager.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionIdManager.java index e25c80fe137..f8e7418b915 100644 --- a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionIdManager.java +++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionIdManager.java @@ -48,16 +48,15 @@ import com.mongodb.Mongo; import com.mongodb.MongoException; /** - * Based partially on the jdbc session id manager... + * Based partially on the JDBCSessionIdManager. * * Theory is that we really only need the session id manager for the local * instance so we have something to scavenge on, namely the list of known ids * - * this class has a timer that runs at the scavenge delay that runs a query - * for all id's known to this node and that have and old accessed value greater - * then the scavengeDelay. + * This class has a timer that runs a periodic scavenger thread to query + * for all id's known to this node whose precalculated expiry time has passed. * - * these found sessions are then run through the invalidateAll(id) method that + * These found sessions are then run through the invalidateAll(id) method that * is a bit hinky but is supposed to notify all handlers this id is now DOA and * ought to be cleaned up. this ought to result in a save operation on the session * that will change the valid field to false (this conjecture is unvalidated atm) @@ -150,27 +149,28 @@ public class MongoSessionIdManager extends AbstractSessionIdManager protected void scavenge() { long now = System.currentTimeMillis(); - __log.debug("SessionIdManager:scavenge:at " + now); + __log.debug("SessionIdManager:scavenge:at {}", now); synchronized (_sessionsIds) { /* * run a query returning results that: * - are in the known list of sessionIds - * - have an accessed time less then current time - the scavenger period + * - 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.__ACCESSED, new BasicDBObject("$lt",now - _scavengePeriod)); + 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)); for ( DBObject session : checkSessions ) { - __log.debug("SessionIdManager:scavenge: invalidating " + (String)session.get(MongoSessionManager.__ID)); - invalidateAll((String)session.get(MongoSessionManager.__ID)); + __log.debug("SessionIdManager:scavenge: expiring session {}", (String)session.get(MongoSessionManager.__ID)); + expireAll((String)session.get(MongoSessionManager.__ID)); } } @@ -178,12 +178,10 @@ public class MongoSessionIdManager extends AbstractSessionIdManager /* ------------------------------------------------------------ */ /** - * ScavengeFully is a process that periodically checks the tracked session - * ids of this given instance of the session id manager to see if they - * are past the point of expiration. + * ScavengeFully will expire all sessions. In most circumstances + * you should never need to call this method. * - * NOTE: this is potentially devastating and may lead to serious session - * coherence issues, not to be used in a running cluster + * USE WITH CAUTION */ protected void scavengeFully() { @@ -193,7 +191,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager for (DBObject session : checkSessions) { - invalidateAll((String)session.get(MongoSessionManager.__ID)); + expireAll((String)session.get(MongoSessionManager.__ID)); } } @@ -205,7 +203,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager * * There are two checks being done here: * - * - if the accessed time is older then the current time minus the purge invalid age + * - if the accessed time is older than the current time minus the purge invalid age * and it is no longer valid then remove that session * - if the accessed time is older then the current time minus the purge valid age * then we consider this a lost record and remove it @@ -229,7 +227,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager { String id = (String)session.get("id"); - __log.debug("MongoSessionIdManager:purging invalid " + id); + __log.debug("MongoSessionIdManager:purging invalid session {}", id); _sessions.remove(session); } @@ -247,7 +245,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager { String id = (String)session.get(MongoSessionManager.__ID); - __log.debug("MongoSessionIdManager:purging valid " + id); + __log.debug("MongoSessionIdManager:purging valid session {}", id); _sessions.remove(session); } @@ -264,7 +262,6 @@ public class MongoSessionIdManager extends AbstractSessionIdManager protected void purgeFully() { BasicDBObject invalidQuery = new BasicDBObject(); - invalidQuery.put(MongoSessionManager.__VALID, false); DBCursor oldSessions = _sessions.find(invalidQuery, new BasicDBObject(MongoSessionManager.__ID, 1)); @@ -273,7 +270,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager { String id = (String)session.get(MongoSessionManager.__ID); - __log.debug("MongoSessionIdManager:purging invalid " + id); + __log.debug("MongoSessionIdManager:purging invalid session {}", id); _sessions.remove(session); } @@ -302,6 +299,8 @@ public class MongoSessionIdManager extends AbstractSessionIdManager /* ------------------------------------------------------------ */ /** + * The delay before the first scavenge operation is performed. + * * sets the scavengeDelay */ public void setScavengeDelay(long scavengeDelay) @@ -314,6 +313,11 @@ public class MongoSessionIdManager extends AbstractSessionIdManager /* ------------------------------------------------------------ */ + /** + * The period in seconds between scavenge checks. + * + * @param scavengePeriod + */ public void setScavengePeriod(long scavengePeriod) { if (scavengePeriod <= 0) @@ -448,7 +452,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager /* ------------------------------------------------------------ */ /** - * is the session id known to mongo, and is it valid + * Searches database to find if the session id known to mongo, and is it valid */ @Override public boolean idInUse(String sessionId) @@ -461,11 +465,10 @@ public class MongoSessionIdManager extends AbstractSessionIdManager if ( o != null ) { Boolean valid = (Boolean)o.get(MongoSessionManager.__VALID); - if ( valid == null ) { return false; - } + } return valid; } @@ -486,7 +489,7 @@ public class MongoSessionIdManager extends AbstractSessionIdManager * already a part of the index in mongo... */ - __log.debug("MongoSessionIdManager:addSession:" + session.getId()); + __log.debug("MongoSessionIdManager:addSession {}", session.getId()); synchronized (_sessionsIds) { @@ -511,14 +514,18 @@ public class MongoSessionIdManager extends AbstractSessionIdManager } /* ------------------------------------------------------------ */ + /** Remove the session id from the list of in-use sessions. + * Inform all other known contexts that sessions with the same id should be + * invalidated. + * @see org.eclipse.jetty.server.SessionIdManager#invalidateAll(java.lang.String) + */ @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); @@ -537,6 +544,38 @@ public class MongoSessionIdManager extends AbstractSessionIdManager } } } + + /* ------------------------------------------------------------ */ + /** + * Expire this session for all contexts that are sharing the session + * id. + * @param sessionId + */ + 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 + *
  • "id" : session_id
  • + *
  • "created" : create_time
  • + *
  • "accessed": last_access_time
  • + *
  • "maxIdle" : max_idle_time setting as session was created
  • + *
  • "expiry" : time at which session should expire
  • + *
  • "valid" : session_valid
  • + *
  • "context" : a nested object containing 1 nested object per context for which the session id is in use + * + * Each of the nested objects inside the "context" element contains: + *
      + *
    • unique_context_name : nested object containing name:value pairs of the session attributes for that context
    • + *
    + *

    + * One of the name:value attribute pairs will always be the special attribute "__metadata__". The value + * is an object representing a version counter which is incremented every time the attributes change. + *

    + *

    + * For example: + * + * { "_id" : ObjectId("52845534a40b66410f228f23"), + * "accessed" : NumberLong("1384818548903"), + * "maxIdle" : 1, + * "context" : { "::/contextA" : { "A" : "A", + * "__metadata__" : { "version" : NumberLong(2) } + * }, + * "::/contextB" : { "B" : "B", + * "__metadata__" : { "version" : NumberLong(1) } + * } + * }, + * "created" : NumberLong("1384818548903"), + * "expiry" : NumberLong("1384818549903"), + * "id" : "w01ijx2vnalgv1sqrpjwuirprp7", + * "valid" : true + * } + * + *

    + *

    + * In MongoDB, the nesting level is indicated by "." separators for the key name. Thus to + * interact with a session attribute, the key is composed of: + * "context".unique_context_name.attribute_name + * Eg "context"."::/contextA"."A" + *

    + */ @ManagedObject("Mongo Session Manager") public class MongoSessionManager extends NoSqlSessionManager { @@ -54,14 +103,56 @@ public class MongoSessionManager extends NoSqlSessionManager /* * strings used as keys or parts of keys in mongo */ + /** + * Special attribute for a session that is context-specific + */ private final static String __METADATA = "__metadata__"; + + /** + * Session id + */ public final static String __ID = "id"; + + /** + * Time of session creation + */ private final static String __CREATED = "created"; + + /** + * Whether or not session is valid + */ public final static String __VALID = "valid"; + + /** + * Time at which session was invalidated + */ public final static String __INVALIDATED = "invalidated"; + + /** + * Last access time of session + */ public final static String __ACCESSED = "accessed"; + + /** + * Time this session will expire, based on last access time and maxIdle + */ + public final static String __EXPIRY = "expiry"; + + /** + * The max idle time of a session (smallest value across all contexts which has a session with the same id) + */ + public final static String __MAX_IDLE = "maxIdle"; + + /** + * Name of nested document field containing 1 sub document per context for which the session id is in use + */ private final static String __CONTEXT = "context"; + + + /** + * Special attribute per session per context, incremented each time attributes are modified + */ public final static String __VERSION = __METADATA + ".version"; /** @@ -70,8 +161,16 @@ public class MongoSessionManager extends NoSqlSessionManager private String _contextId = null; - private DBCollection _sessions; - private DBObject __version_1; + /** + * Access to MongoDB + */ + private DBCollection _dbSessions; + + + /** + * Utility value of 1 for a session version for this context + */ + private DBObject _version_1; /* ------------------------------------------------------------ */ @@ -88,9 +187,7 @@ public class MongoSessionManager extends NoSqlSessionManager { super.doStart(); String[] hosts = getContextHandler().getVirtualHosts(); - //TODO: can this be replaced? - /*if (hosts == null || hosts.length == 0) - hosts = getContextHandler().getConnectorNames();*/ + if (hosts == null || hosts.length == 0) hosts = new String[] { "::" }; // IPv6 equiv of 0.0.0.0 @@ -102,19 +199,18 @@ public class MongoSessionManager extends NoSqlSessionManager } _contextId = createContextId(hosts,contextPath); - - __version_1 = new BasicDBObject(getContextKey(__VERSION),1); + _version_1 = new BasicDBObject(getContextAttributeKey(__VERSION),1); } /* ------------------------------------------------------------ */ - /* (non-Javadoc) + /** * @see org.eclipse.jetty.server.session.AbstractSessionManager#setSessionIdManager(org.eclipse.jetty.server.SessionIdManager) */ @Override public void setSessionIdManager(SessionIdManager metaManager) { MongoSessionIdManager msim = (MongoSessionIdManager)metaManager; - _sessions=msim.getSessions(); + _dbSessions=msim.getSessions(); super.setSessionIdManager(metaManager); } @@ -125,7 +221,7 @@ public class MongoSessionManager extends NoSqlSessionManager { try { - __log.debug("MongoSessionManager:save:" + session); + __log.debug("MongoSessionManager:save session {}", session.getClusterId()); session.willPassivate(); // Form query for upsert @@ -137,9 +233,13 @@ public class MongoSessionManager extends NoSqlSessionManager BasicDBObject sets = new BasicDBObject(); BasicDBObject unsets = new BasicDBObject(); + // handle valid or invalid if (session.isValid()) { + long expiry = (session.getMaxInactiveInterval() > 0?(session.getAccessed()+(1000*getMaxInactiveInterval())):0); + __log.debug("MongoSessionManager: calculated expiry {} for session {}", expiry, session.getId()); + // handle new or existing if (version == null) { @@ -148,12 +248,26 @@ public class MongoSessionManager extends NoSqlSessionManager version = new Long(1); sets.put(__CREATED,session.getCreationTime()); sets.put(__VALID,true); - sets.put(getContextKey(__VERSION),version); + + sets.put(getContextAttributeKey(__VERSION),version); + sets.put(__MAX_IDLE, getMaxInactiveInterval()); + sets.put(__EXPIRY, expiry); } else { version = new Long(((Number)version).longValue() + 1); - update.put("$inc",__version_1); + update.put("$inc",_version_1); + //if max idle time and/or expiry is smaller for this context, then choose that for the whole session doc + BasicDBObject fields = new BasicDBObject(); + fields.append(__MAX_IDLE, true); + fields.append(__EXPIRY, true); + DBObject o = _dbSessions.findOne(new BasicDBObject("id",session.getClusterId()), fields); + Integer currentMaxIdle = (Integer)o.get(__MAX_IDLE); + Long currentExpiry = (Long)o.get(__EXPIRY); + if (currentMaxIdle != null && getMaxInactiveInterval() > 0 && getMaxInactiveInterval() < currentMaxIdle) + sets.put(__MAX_IDLE, getMaxInactiveInterval()); + if (currentExpiry != null && expiry > 0 && expiry < currentExpiry) + sets.put(__EXPIRY, currentExpiry); } sets.put(__ACCESSED,session.getAccessed()); @@ -185,8 +299,8 @@ public class MongoSessionManager extends NoSqlSessionManager if (!unsets.isEmpty()) update.put("$unset",unsets); - _sessions.update(key,update,upsert,false); - __log.debug("MongoSessionManager:save:db.sessions.update(" + key + "," + update + ",true)"); + _dbSessions.update(key,update,upsert,false); + __log.debug("MongoSessionManager:save:db.sessions.update( {}, {}, true) ", key, update); if (activateAfterSave) session.didActivate(); @@ -204,20 +318,20 @@ public class MongoSessionManager extends NoSqlSessionManager @Override protected Object refresh(NoSqlSession session, Object version) { - __log.debug("MongoSessionManager:refresh " + session); + __log.debug("MongoSessionManager:refresh session {}", session.getId()); // check if our in memory version is the same as what is on the disk if (version != null) { - DBObject o = _sessions.findOne(new BasicDBObject(__ID,session.getClusterId()),__version_1); + DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,session.getClusterId()),_version_1); if (o != null) { - Object saved = getNestedValue(o, getContextKey(__VERSION)); + Object saved = getNestedValue(o, getContextAttributeKey(__VERSION)); if (saved != null && saved.equals(version)) { - __log.debug("MongoSessionManager:refresh not needed"); + __log.debug("MongoSessionManager:refresh not needed session {}", session.getId()); return version; } version = saved; @@ -225,12 +339,12 @@ public class MongoSessionManager extends NoSqlSessionManager } // If we are here, we have to load the object - DBObject o = _sessions.findOne(new BasicDBObject(__ID,session.getClusterId())); + DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,session.getClusterId())); // If it doesn't exist, invalidate if (o == null) { - __log.debug("MongoSessionManager:refresh:marking invalid, no object"); + __log.debug("MongoSessionManager:refresh:marking session {} invalid, no object", session.getClusterId()); session.invalidate(); return null; } @@ -239,7 +353,7 @@ public class MongoSessionManager extends NoSqlSessionManager Boolean valid = (Boolean)o.get(__VALID); if (valid == null || !valid) { - __log.debug("MongoSessionManager:refresh:marking invalid, valid flag " + valid); + __log.debug("MongoSessionManager:refresh:marking session {} invalid, valid flag {}", session.getClusterId(), valid); session.invalidate(); return null; } @@ -251,17 +365,15 @@ public class MongoSessionManager extends NoSqlSessionManager { session.clearAttributes(); - DBObject attrs = (DBObject)getNestedValue(o,getContextKey()); - + DBObject attrs = (DBObject)getNestedValue(o,getContextKey()); if (attrs != null) { for (String name : attrs.keySet()) { + //skip special metadata field which is not one of the session attributes if (__METADATA.equals(name)) - { continue; - } String attr = decodeName(name); Object value = decodeValue(attrs.get(name)); @@ -301,7 +413,7 @@ public class MongoSessionManager extends NoSqlSessionManager update.put("$set",sets); } - _sessions.update(key,update,false,false); + _dbSessions.update(key,update,false,false); session.didActivate(); @@ -319,51 +431,51 @@ public class MongoSessionManager extends NoSqlSessionManager @Override protected synchronized NoSqlSession loadSession(String clusterId) { - DBObject o = _sessions.findOne(new BasicDBObject(__ID,clusterId)); - - __log.debug("MongoSessionManager:loaded " + o); + DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,clusterId)); + __log.debug("MongoSessionManager:id={} loaded={}", clusterId, o); if (o == null) - { return null; - } Boolean valid = (Boolean)o.get(__VALID); + __log.debug("MongoSessionManager:id={} valid={}", clusterId, valid); if (valid == null || !valid) - { return null; - } try { - Object version = o.get(getContextKey(__VERSION)); + Object version = o.get(getContextAttributeKey(__VERSION)); Long created = (Long)o.get(__CREATED); Long accessed = (Long)o.get(__ACCESSED); - NoSqlSession session = new NoSqlSession(this,created,accessed,clusterId,version); + NoSqlSession session = null; - // get the attributes for the context + // get the session for the context DBObject attrs = (DBObject)getNestedValue(o,getContextKey()); - __log.debug("MongoSessionManager:attrs: " + attrs); + __log.debug("MongoSessionManager:attrs {}", attrs); if (attrs != null) { + __log.debug("MongoSessionManager: session {} present for context {}", clusterId, getContextKey()); + //only load a session if it exists for this context + session = new NoSqlSession(this,created,accessed,clusterId,version); + for (String name : attrs.keySet()) { + //skip special metadata attribute which is not one of the actual session attributes if ( __METADATA.equals(name) ) - { continue; - } String attr = decodeName(name); Object value = decodeValue(attrs.get(name)); session.doPutOrRemove(attr,value); session.bindValue(attr,value); - } + session.didActivate(); } - session.didActivate(); + else + __log.debug("MongoSessionManager: session {} not present for context {}",clusterId, getContextKey()); return session; } @@ -374,11 +486,17 @@ public class MongoSessionManager extends NoSqlSessionManager return null; } + + /*------------------------------------------------------------ */ + /** + * Remove the per-context sub document for this session id. + * @see org.eclipse.jetty.nosql.NoSqlSessionManager#remove(org.eclipse.jetty.nosql.NoSqlSession) + */ @Override protected boolean remove(NoSqlSession session) { - __log.debug("MongoSessionManager:remove:session " + session.getClusterId()); + __log.debug("MongoSessionManager:remove:session {} for context {}",session.getClusterId(), getContextKey()); /* * Check if the session exists and if it does remove the context @@ -386,7 +504,7 @@ public class MongoSessionManager extends NoSqlSessionManager */ BasicDBObject key = new BasicDBObject(__ID,session.getClusterId()); - DBObject o = _sessions.findOne(key,__version_1); + DBObject o = _dbSessions.findOne(key,_version_1); if (o != null) { @@ -394,7 +512,7 @@ public class MongoSessionManager extends NoSqlSessionManager BasicDBObject unsets = new BasicDBObject(); unsets.put(getContextKey(),1); remove.put("$unset",unsets); - _sessions.update(key,remove); + _dbSessions.update(key,remove); return true; } @@ -404,20 +522,22 @@ public class MongoSessionManager extends NoSqlSessionManager } } - /*------------------------------------------------------------ */ + + + /** + * @see org.eclipse.jetty.nosql.NoSqlSessionManager#expire(java.lang.String) + */ @Override - protected void invalidateSession(String idInCluster) + protected void expire (String idInCluster) { - __log.debug("MongoSessionManager:invalidateSession:invalidating " + idInCluster); + __log.debug("MongoSessionManager:expire session {} ", idInCluster); + + //Expire the session for this context + super.expire(idInCluster); - super.invalidateSession(idInCluster); - - /* - * pull back the 'valid' value, we can check if its false, if is we don't need to - * reset it to false - */ + //If the outer session document has not already been marked invalid, do so. DBObject validKey = new BasicDBObject(__VALID, true); - DBObject o = _sessions.findOne(new BasicDBObject(__ID,idInCluster), validKey); + DBObject o = _dbSessions.findOne(new BasicDBObject(__ID,idInCluster), validKey); if (o != null && (Boolean)o.get(__VALID)) { @@ -428,21 +548,24 @@ public class MongoSessionManager extends NoSqlSessionManager update.put("$set",sets); BasicDBObject key = new BasicDBObject(__ID,idInCluster); - - _sessions.update(key,update); + _dbSessions.update(key,update); } } + /*------------------------------------------------------------ */ + /** + * Change the session id. Note that this will change the session id for all contexts for which the session id is in use. + * @see org.eclipse.jetty.nosql.NoSqlSessionManager#update(org.eclipse.jetty.nosql.NoSqlSession, java.lang.String, java.lang.String) + */ @Override protected void update(NoSqlSession session, String newClusterId, String newNodeId) throws Exception { - // Form query for update - use object's existing session id BasicDBObject key = new BasicDBObject(__ID, session.getClusterId()); BasicDBObject sets = new BasicDBObject(); BasicDBObject update = new BasicDBObject(__ID, newClusterId); sets.put("$set", update); - _sessions.update(key, sets, false, false); + _dbSessions.update(key, sets, false, false); } /*------------------------------------------------------------ */ @@ -527,9 +650,13 @@ public class MongoSessionManager extends NoSqlSessionManager } /*------------------------------------------------------------ */ - private String getContextKey(String keybit) + /** Get a dot separated key for + * @param key + * @return + */ + private String getContextAttributeKey(String attr) { - return __CONTEXT + "." + _contextId + "." + keybit; + return getContextKey()+ "." + attr; } @ManagedOperation(value="purge invalid sessions in the session store based on normal criteria", impact="ACTION") @@ -567,7 +694,7 @@ public class MongoSessionManager extends NoSqlSessionManager @ManagedAttribute("total number of known sessions in the store") public long getSessionStoreCount() { - return _sessions.find().count(); + return _dbSessions.find().count(); } /*------------------------------------------------------------ */ diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java index b13419bc211..e5414b89d65 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java @@ -276,7 +276,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement { super.doStop(); - invalidateSessions(); + shutdownSessions(); _loader=null; } @@ -735,7 +735,12 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement */ public abstract AbstractSession getSession(String idInCluster); - protected abstract void invalidateSessions() throws Exception; + /** + * Prepare sessions for session manager shutdown + * + * @throws Exception + */ + protected abstract void shutdownSessions() throws Exception; /* ------------------------------------------------------------ */ @@ -783,7 +788,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and * {@link SessionIdManager#invalidateAll(String)} should be called. */ - public void removeSession(AbstractSession session, boolean invalidate) + public boolean removeSession(AbstractSession session, boolean invalidate) { // Remove session from context and global maps boolean removed = removeSession(session.getClusterId()); @@ -807,6 +812,8 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement } } } + + return removed; } /* ------------------------------------------------------------ */ diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java index 65bcf1f0713..eb831795b50 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java @@ -388,7 +388,7 @@ public class HashSessionManager extends AbstractSessionManager /* ------------------------------------------------------------ */ @Override - protected void invalidateSessions() throws Exception + protected void shutdownSessions() throws Exception { // Invalidate all sessions to cause unbind events ArrayList sessions=new ArrayList(_sessions.values()); @@ -398,11 +398,11 @@ public class HashSessionManager extends AbstractSessionManager // If we are called from doStop if (isStopping() && _storeDir != null && _storeDir.exists() && _storeDir.canWrite()) { - // Then we only save and remove the session - it is not invalidated. + // Then we only save and remove the session from memory- it is not invalidated. for (HashedSession session : sessions) { session.save(false); - removeSession(session,false); + _sessions.remove(session.getClusterId()); } } else diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java index b6057a236c0..1a293051be4 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java @@ -27,11 +27,13 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.servlet.http.HttpServletRequest; @@ -72,7 +74,7 @@ public class JDBCSessionManager extends AbstractSessionManager { private static final Logger LOG = Log.getLogger(JDBCSessionManager.class); - private ConcurrentHashMap _sessions; + private ConcurrentHashMap _sessions; protected JDBCSessionIdManager _jdbcSessionIdMgr = null; protected long _saveIntervalSec = 60; //only persist changes to session access times every 60 secs @@ -326,9 +328,7 @@ public class JDBCSessionManager extends AbstractSessionManager { //The session attributes have changed, write to the db, ensuring //http passivation/activation listeners called - willPassivate(); - updateSession(this); - didActivate(); + save(true); } else if ((getAccessed() - _lastSaved) >= (getSaveInterval() * 1000L)) { @@ -361,6 +361,23 @@ public class JDBCSessionManager extends AbstractSessionManager } } } + + protected void save (boolean reactivate) throws Exception + { + synchronized (this) + { + if (_dirty) + { + //The session attributes have changed, write to the db, ensuring + //http passivation/activation listeners called + willPassivate(); + updateSession(this); + if (reactivate) + didActivate(); + } + } + } + @Override protected void timeout() throws IllegalStateException @@ -451,109 +468,110 @@ public class JDBCSessionManager extends AbstractSessionManager public Session getSession(String idInCluster) { Session session = null; - Session memSession = (Session)_sessions.get(idInCluster); - + synchronized (this) { - //check if we need to reload the session - - //as an optimization, don't reload on every access - //to reduce the load on the database. This introduces a window of - //possibility that the node may decide that the session is local to it, - //when the session has actually been live on another node, and then - //re-migrated to this node. This should be an extremely rare occurrence, - //as load-balancers are generally well-behaved and consistently send - //sessions to the same node, changing only iff that node fails. - //Session data = null; - long now = System.currentTimeMillis(); - if (LOG.isDebugEnabled()) - { - if (memSession==null) - LOG.debug("getSession("+idInCluster+"): not in session map,"+ - " now="+now+ - " lastSaved="+(memSession==null?0:memSession._lastSaved)+ - " interval="+(_saveIntervalSec * 1000L)); - else - LOG.debug("getSession("+idInCluster+"): in session map, "+ - " now="+now+ - " lastSaved="+(memSession==null?0:memSession._lastSaved)+ - " interval="+(_saveIntervalSec * 1000L)+ - " lastNode="+memSession._lastNode+ - " thisNode="+getSessionIdManager().getWorkerName()+ - " difference="+(now - memSession._lastSaved)); - } + Session memSession = (Session)_sessions.get(idInCluster); + + //check if we need to reload the session - + //as an optimization, don't reload on every access + //to reduce the load on the database. This introduces a window of + //possibility that the node may decide that the session is local to it, + //when the session has actually been live on another node, and then + //re-migrated to this node. This should be an extremely rare occurrence, + //as load-balancers are generally well-behaved and consistently send + //sessions to the same node, changing only iff that node fails. + //Session data = null; + long now = System.currentTimeMillis(); + if (LOG.isDebugEnabled()) + { + if (memSession==null) + LOG.debug("getSession("+idInCluster+"): not in session map,"+ + " now="+now+ + " lastSaved="+(memSession==null?0:memSession._lastSaved)+ + " interval="+(_saveIntervalSec * 1000L)); + else + LOG.debug("getSession("+idInCluster+"): in session map, "+ + " now="+now+ + " lastSaved="+(memSession==null?0:memSession._lastSaved)+ + " interval="+(_saveIntervalSec * 1000L)+ + " lastNode="+memSession._lastNode+ + " thisNode="+getSessionIdManager().getWorkerName()+ + " difference="+(now - memSession._lastSaved)); + } - try + try + { + if (memSession==null) { - if (memSession==null) - { - LOG.debug("getSession("+idInCluster+"): no session in session map. Reloading session data from db."); - session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context)); - } - else if ((now - memSession._lastSaved) >= (_saveIntervalSec * 1000L)) - { - LOG.debug("getSession("+idInCluster+"): stale session. Reloading session data from db."); - session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context)); - } - else - { - LOG.debug("getSession("+idInCluster+"): session in session map"); - session = memSession; - } + LOG.debug("getSession("+idInCluster+"): no session in session map. Reloading session data from db."); + session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context)); } - catch (Exception e) + else if ((now - memSession._lastSaved) >= (_saveIntervalSec * 1000L)) { - LOG.warn("Unable to load session "+idInCluster, e); - return null; - } - - - //If we have a session - if (session != null) - { - //If the session was last used on a different node, or session doesn't exist on this node - if (!session.getLastNode().equals(getSessionIdManager().getWorkerName()) || memSession==null) - { - //if session doesn't expire, or has not already expired, update it and put it in this nodes' memory - if (session._expiryTime <= 0 || session._expiryTime > now) - { - if (LOG.isDebugEnabled()) - LOG.debug("getSession("+idInCluster+"): lastNode="+session.getLastNode()+" thisNode="+getSessionIdManager().getWorkerName()); - - session.setLastNode(getSessionIdManager().getWorkerName()); - _sessions.put(idInCluster, session); - - //update in db - try - { - updateSessionNode(session); - session.didActivate(); - } - catch (Exception e) - { - LOG.warn("Unable to update freshly loaded session "+idInCluster, e); - return null; - } - } - else - { - LOG.debug("getSession ({}): Session has expired", idInCluster); - //ensure that the session id for the expired session is deleted so that a new session with the - //same id cannot be created (because the idInUse() test would succeed) - _jdbcSessionIdMgr.removeSession(idInCluster); - session=null; - } - - } - else - LOG.debug("getSession({}): Session not stale {}", idInCluster,session); + LOG.debug("getSession("+idInCluster+"): stale session. Reloading session data from db."); + session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context)); } else { - //No session in db with matching id and context path. - LOG.debug("getSession({}): No session in database matching id={}",idInCluster,idInCluster); + LOG.debug("getSession("+idInCluster+"): session in session map"); + session = memSession; } + } + catch (Exception e) + { + LOG.warn("Unable to load session "+idInCluster, e); + return null; + } - return session; + + //If we have a session + if (session != null) + { + //If the session was last used on a different node, or session doesn't exist on this node + if (!session.getLastNode().equals(getSessionIdManager().getWorkerName()) || memSession==null) + { + //if session doesn't expire, or has not already expired, update it and put it in this nodes' memory + if (session._expiryTime <= 0 || session._expiryTime > now) + { + if (LOG.isDebugEnabled()) + LOG.debug("getSession("+idInCluster+"): lastNode="+session.getLastNode()+" thisNode="+getSessionIdManager().getWorkerName()); + + session.setLastNode(getSessionIdManager().getWorkerName()); + _sessions.put(idInCluster, session); + + //update in db + try + { + updateSessionNode(session); + session.didActivate(); + } + catch (Exception e) + { + LOG.warn("Unable to update freshly loaded session "+idInCluster, e); + return null; + } + } + else + { + LOG.debug("getSession ({}): Session has expired", idInCluster); + //ensure that the session id for the expired session is deleted so that a new session with the + //same id cannot be created (because the idInUse() test would succeed) + _jdbcSessionIdMgr.removeSession(idInCluster); + session=null; + } + + } + else + LOG.debug("getSession({}): Session not stale {}", idInCluster,session); + } + else + { + //No session in db with matching id and context path. + LOG.debug("getSession({}): No session in database matching id={}",idInCluster,idInCluster); + } + + return session; } } @@ -566,12 +584,7 @@ public class JDBCSessionManager extends AbstractSessionManager @Override public int getSessions() { - int size = 0; - synchronized (this) - { - size = _sessions.size(); - } - return size; + return _sessions.size(); } @@ -588,7 +601,7 @@ public class JDBCSessionManager extends AbstractSessionManager _jdbcSessionIdMgr = (JDBCSessionIdManager)_sessionIdManager; - _sessions = new ConcurrentHashMap(); + _sessions = new ConcurrentHashMap(); super.doStart(); } @@ -602,21 +615,46 @@ public class JDBCSessionManager extends AbstractSessionManager @Override public void doStop() throws Exception { + super.doStop(); _sessions.clear(); _sessions = null; - - super.doStop(); } @Override - protected void invalidateSessions() + protected void shutdownSessions() { - //Do nothing - we don't want to remove and - //invalidate all the sessions because this - //method is called from doStop(), and just - //because this context is stopping does not - //mean that we should remove the session from - //any other nodes + //Save the current state of all of our sessions, + //do NOT delete them (so other nodes can manage them) + long gracefulStopMs = getContextHandler().getServer().getStopTimeout(); + long stopTime = 0; + if (gracefulStopMs > 0) + stopTime = System.nanoTime() + (TimeUnit.NANOSECONDS.convert(gracefulStopMs, TimeUnit.MILLISECONDS)); + + ArrayList sessions = (_sessions == null? new ArrayList() :new ArrayList(_sessions.values()) ); + + // loop while there are sessions, and while there is stop time remaining, or if no stop time, just 1 loop + while (sessions.size() > 0 && ((stopTime > 0 && (System.nanoTime() < stopTime)) || (stopTime == 0))) + { + for (Session session : sessions) + { + try + { + session.save(false); + } + catch (Exception e) + { + LOG.warn(e); + } + _sessions.remove(session.getClusterId()); + } + + //check if we should terminate our loop if we're not using the stop timer + if (stopTime == 0) + break; + + // Get any sessions that were added by other requests during processing and go around the loop again + sessions=new ArrayList(_sessions.values()); + } } @@ -627,23 +665,23 @@ public class JDBCSessionManager extends AbstractSessionManager public void renewSessionId (String oldClusterId, String oldNodeId, String newClusterId, String newNodeId) { Session session = null; - synchronized (this) + try { - try + session = (Session)_sessions.remove(oldClusterId); + if (session != null) { - session = (Session)_sessions.remove(oldClusterId); - if (session != null) + synchronized (session) { session.setClusterId(newClusterId); //update ids session.setNodeId(newNodeId); _sessions.put(newClusterId, session); //put it into list in memory - session.save(); //update database + updateSession(session); //update database } } - catch (Exception e) - { - LOG.warn(e); - } + } + catch (Exception e) + { + LOG.warn(e); } super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId); @@ -658,11 +696,7 @@ public class JDBCSessionManager extends AbstractSessionManager */ protected void invalidateSession (String idInCluster) { - Session session = null; - synchronized (this) - { - session = (Session)_sessions.get(idInCluster); - } + Session session = (Session)_sessions.get(idInCluster); if (session != null) { @@ -679,20 +713,17 @@ public class JDBCSessionManager extends AbstractSessionManager @Override protected boolean removeSession(String idInCluster) { - synchronized (this) + Session session = (Session)_sessions.remove(idInCluster); + try { - Session session = (Session)_sessions.remove(idInCluster); - try - { - if (session != null) - deleteSession(session); - } - catch (Exception e) - { - LOG.warn("Problem deleting session id="+idInCluster, e); - } - return session!=null; + if (session != null) + deleteSession(session); } + catch (Exception e) + { + LOG.warn("Problem deleting session id="+idInCluster, e); + } + return session!=null; } @@ -707,13 +738,8 @@ public class JDBCSessionManager extends AbstractSessionManager if (session==null) return; - synchronized (this) - { - _sessions.put(session.getClusterId(), session); - } + _sessions.put(session.getClusterId(), (Session)session); - //TODO or delay the store until exit out of session? If we crash before we store it - //then session data will be lost. try { synchronized (session) @@ -748,40 +774,20 @@ public class JDBCSessionManager extends AbstractSessionManager * {@link SessionIdManager#invalidateAll(String)} should be called. */ @Override - public void removeSession(AbstractSession session, boolean invalidate) + public boolean removeSession(AbstractSession session, boolean invalidate) { // Remove session from context and global maps - boolean removed = false; - - synchronized (this) - { - //take this session out of the map of sessions for this context - if (_sessions.containsKey(session.getClusterId())) - { - removed = true; - removeSession(session.getClusterId()); - } - } + boolean removed = super.removeSession(session, invalidate); if (removed) { - // Remove session from all context and global id maps - _sessionIdManager.removeSession(session); - - if (invalidate) - _sessionIdManager.invalidateAll(session.getClusterId()); - - if (invalidate && !_sessionListeners.isEmpty()) - { - HttpSessionEvent event=new HttpSessionEvent(session); - for (HttpSessionListener l : _sessionListeners) - l.sessionDestroyed(event); - } if (!invalidate) { session.willPassivate(); } } + + return removed; } @@ -822,10 +828,7 @@ public class JDBCSessionManager extends AbstractSessionManager { //loaded an expired session last managed on this node for this context, add it to the list so we can //treat it like a normal expired session - synchronized (this) - { - _sessions.put(session.getClusterId(), session); - } + _sessions.put(session.getClusterId(), session); } else { @@ -834,7 +837,7 @@ public class JDBCSessionManager extends AbstractSessionManager continue; } } - + if (session != null) { session.timeout(); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionCookieTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionCookieTest.java index 0c52028ed53..1590d27ae54 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionCookieTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionCookieTest.java @@ -122,10 +122,10 @@ public class SessionCookieTest } /** - * @see org.eclipse.jetty.server.session.AbstractSessionManager#invalidateSessions() + * @see org.eclipse.jetty.server.session.AbstractSessionManager#shutdownSessions() */ @Override - protected void invalidateSessions() throws Exception + protected void shutdownSessions() throws Exception { } diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestServer.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestServer.java index 2fd52d97a16..3305e006d5a 100644 --- a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestServer.java +++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/JdbcTestServer.java @@ -84,7 +84,6 @@ public class JdbcTestServer extends AbstractTestServer idManager.setScavengeInterval(_scavengePeriod); idManager.setWorkerName("w"+(__workers++)); idManager.setDriverInfo(DRIVER_CLASS, (config==null?DEFAULT_CONNECTION_URL:config)); - //System.err.println("new jdbcidmgr inst="+idManager); return idManager; } } @@ -102,7 +101,7 @@ public class JdbcTestServer extends AbstractTestServer } - public boolean existsInSessionTable(String id) + public boolean existsInSessionIdTable(String id) throws Exception { Class.forName(DRIVER_CLASS); @@ -122,6 +121,39 @@ public class JdbcTestServer extends AbstractTestServer } } + + public boolean existsInSessionTable(String id, boolean verbose) + throws Exception + { + Class.forName(DRIVER_CLASS); + Connection con = null; + try + { + con = DriverManager.getConnection(DEFAULT_CONNECTION_URL); + PreparedStatement statement = con.prepareStatement("select * from "+((JDBCSessionIdManager)_sessionIdManager)._sessionTable+" where sessionId = ?"); + statement.setString(1, id); + ResultSet result = statement.executeQuery(); + if (verbose) + { + boolean results = false; + while (result.next()) + { + results = true; + } + return results; + } + else + return result.next(); + } + finally + { + if (con != null) + con.close(); + } + } + + + public Set getSessionIds () throws Exception { diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionInvalidateAndCreateTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionInvalidateAndCreateTest.java new file mode 100644 index 00000000000..83b273719b2 --- /dev/null +++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/SessionInvalidateAndCreateTest.java @@ -0,0 +1,38 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + + +package org.eclipse.jetty.server.session; + +import org.junit.Test; + +public class SessionInvalidateAndCreateTest extends AbstractSessionInvalidateAndCreateTest +{ + + @Override + public AbstractTestServer createServer(int port, int max, int scavenge) + { + return new JdbcTestServer(port,max,scavenge); + } + + @Test + public void testSessionScavenge() throws Exception + { + super.testSessionScavenge(); + } +} diff --git a/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/StopSessionManagerPreserveSessionTest.java b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/StopSessionManagerPreserveSessionTest.java new file mode 100644 index 00000000000..21d39d7c2dd --- /dev/null +++ b/tests/test-sessions/test-jdbc-sessions/src/test/java/org/eclipse/jetty/server/session/StopSessionManagerPreserveSessionTest.java @@ -0,0 +1,81 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.session; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.sql.DriverManager; +import java.sql.SQLException; + +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.junit.After; +import org.junit.Test; + +public class StopSessionManagerPreserveSessionTest extends AbstractStopSessionManagerPreserveSessionTest +{ + JdbcTestServer _server; + + @After + public void tearDown() throws Exception + { + try + { + DriverManager.getConnection( "jdbc:derby:sessions;shutdown=true" ); + } + catch( SQLException expected ) + { + } + } + + @Override + public void checkSessionPersisted(boolean expected) + { + try + { + boolean actual = _server.existsInSessionTable(_id, true); + System.err.println(expected+":"+actual); + assertEquals(expected, actual); + } + catch (Exception e) + { + fail(e.getMessage()); + } + } + + @Override + public AbstractTestServer createServer(int port) + { + _server = new JdbcTestServer(0); + return _server; + } + + @Override + public void configureSessionManagement(ServletContextHandler context) + { + //nothing special + } + + + @Test + public void testStopSessionManagerPreserveSession() throws Exception + { + super.testStopSessionManagerPreserveSession(); + } +} diff --git a/tests/test-sessions/test-mongodb-sessions/pom.xml b/tests/test-sessions/test-mongodb-sessions/pom.xml index 19a8594d040..d20923c6263 100644 --- a/tests/test-sessions/test-mongodb-sessions/pom.xml +++ b/tests/test-sessions/test-mongodb-sessions/pom.xml @@ -21,7 +21,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.1.0-SNAPSHOT + 9.1.1-SNAPSHOT test-mongodb-sessions Jetty Tests :: Sessions :: Mongo diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/InvalidateSessionTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/InvalidateSessionTest.java new file mode 100644 index 00000000000..b7bf64c87d6 --- /dev/null +++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/InvalidateSessionTest.java @@ -0,0 +1,54 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.nosql.mongodb; + + +import org.eclipse.jetty.server.session.AbstractInvalidationSessionTest; +import org.eclipse.jetty.server.session.AbstractTestServer; +import org.junit.Test; + +public class InvalidateSessionTest extends AbstractInvalidationSessionTest +{ + + @Override + public AbstractTestServer createServer(int port) + { + return new MongoTestServer(port); + } + + @Override + public void pause() + { + try + { + Thread.currentThread().sleep(2000); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + @Test + public void testInvalidation() throws Exception + { + super.testInvalidation(); + } + +} diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/LocalSessionScavengingTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/LocalSessionScavengingTest.java new file mode 100644 index 00000000000..254af70e246 --- /dev/null +++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/LocalSessionScavengingTest.java @@ -0,0 +1,41 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.nosql.mongodb; + +import org.eclipse.jetty.server.session.AbstractLocalSessionScavengingTest; +import org.eclipse.jetty.server.session.AbstractTestServer; + +public class LocalSessionScavengingTest extends AbstractLocalSessionScavengingTest +{ + + @Override + public AbstractTestServer createServer(int port, int max, int scavenge) + { + return new MongoTestServer(port,max,scavenge); + } + + @Override + public void testLocalSessionsScavenging() throws Exception + { + super.testLocalSessionsScavenging(); + } + + + +} diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestServer.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestServer.java index 2f4d4d4ef98..d118c4ba0a7 100644 --- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestServer.java +++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/MongoTestServer.java @@ -58,11 +58,11 @@ public class MongoTestServer extends AbstractTestServer { try { - System.err.println("MongoTestServer:SessionIdManager scavenge: delay:"+ _scavengePeriod + " period:"+_maxInactivePeriod); + System.err.println("MongoTestServer:SessionIdManager scavenge: delay:"+ _scavengePeriod + " period:"+_scavengePeriod); MongoSessionIdManager idManager = new MongoSessionIdManager(_server); idManager.setWorkerName("w"+(__workers++)); idManager.setScavengeDelay((_scavengePeriod)); - idManager.setScavengePeriod(_maxInactivePeriod); + idManager.setScavengePeriod(_scavengePeriod); return idManager; } diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeInvalidSessionTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeInvalidSessionTest.java index 76c93354894..421bbd2f013 100644 --- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeInvalidSessionTest.java +++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/PurgeInvalidSessionTest.java @@ -145,26 +145,31 @@ public class PurgeInvalidSessionTest if ("create".equals(action)) { HttpSession session = request.getSession(true); + session.setAttribute("foo", "bar"); assertTrue(session.isNew()); } else if ("invalidate".equals(action)) { HttpSession existingSession = request.getSession(false); assertNotNull(existingSession); + String id = existingSession.getId(); + id = (id.indexOf(".") > 0?id.substring(0, id.indexOf(".")):id); + DBObject dbSession = _sessions.findOne(new BasicDBObject("id",id)); + assertNotNull(dbSession); + existingSession.invalidate(); - String id = request.getRequestedSessionId(); - assertNotNull(id); - id = id.substring(0, id.indexOf(".")); //still in db, just marked as invalid - DBObject dbSession = _sessions.findOne(new BasicDBObject("id", id)); - assertTrue(dbSession != null); + dbSession = _sessions.findOne(new BasicDBObject("id", id)); + assertNotNull(dbSession); + assertTrue(dbSession.containsField(MongoSessionManager.__INVALIDATED)); } else if ("test".equals(action)) { String id = request.getRequestedSessionId(); assertNotNull(id); - id = id.substring(0, id.indexOf(".")); + + id = (id.indexOf(".") > 0?id.substring(0, id.indexOf(".")):id); HttpSession existingSession = request.getSession(false); assertTrue(existingSession == null); @@ -175,5 +180,4 @@ public class PurgeInvalidSessionTest } } } - } diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionExpiryTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionExpiryTest.java new file mode 100644 index 00000000000..fa125054b57 --- /dev/null +++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionExpiryTest.java @@ -0,0 +1,45 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.nosql.mongodb; + +import org.eclipse.jetty.server.session.AbstractSessionExpiryTest; +import org.eclipse.jetty.server.session.AbstractTestServer; +import org.junit.Test; + +public class SessionExpiryTest extends AbstractSessionExpiryTest +{ + + @Override + public AbstractTestServer createServer(int port, int max, int scavenge) + { + return new MongoTestServer(port,max,scavenge); + } + + @Test + public void testSessionNotExpired() throws Exception + { + super.testSessionNotExpired(); + } + + @Test + public void testSessionExpiry() throws Exception + { + super.testSessionExpiry(); + } +} diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionInvalidateAndCreateTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionInvalidateAndCreateTest.java new file mode 100644 index 00000000000..e4795f6ddb0 --- /dev/null +++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionInvalidateAndCreateTest.java @@ -0,0 +1,41 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.nosql.mongodb; + +import org.eclipse.jetty.server.session.AbstractSessionInvalidateAndCreateTest; +import org.eclipse.jetty.server.session.AbstractTestServer; +import org.junit.Ignore; +import org.junit.Test; + +public class SessionInvalidateAndCreateTest extends AbstractSessionInvalidateAndCreateTest +{ + + @Override + public AbstractTestServer createServer(int port, int max, int scavenge) + { + return new MongoTestServer(port,max,scavenge); + } + + + @Test + public void testSessionScavenge() throws Exception + { + super.testSessionScavenge(); + } +} diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionMigrationTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionMigrationTest.java new file mode 100644 index 00000000000..3c390d8c8b9 --- /dev/null +++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/SessionMigrationTest.java @@ -0,0 +1,39 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.nosql.mongodb; + +import org.eclipse.jetty.server.session.AbstractSessionMigrationTest; +import org.eclipse.jetty.server.session.AbstractTestServer; +import org.junit.Test; + +public class SessionMigrationTest extends AbstractSessionMigrationTest +{ + + @Override + public AbstractTestServer createServer(int port) + { + return new MongoTestServer(port); + } + + @Test + public void testSessionMigration() throws Exception + { + super.testSessionMigration(); + } +} diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/StopSessionManagerDeleteSessionTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/StopSessionManagerDeleteSessionTest.java new file mode 100644 index 00000000000..baa1f1b1eeb --- /dev/null +++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/StopSessionManagerDeleteSessionTest.java @@ -0,0 +1,164 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.nosql.mongodb; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.net.UnknownHostException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.Test; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBCollection; +import com.mongodb.DBObject; +import com.mongodb.Mongo; +import com.mongodb.MongoException; + +public class StopSessionManagerDeleteSessionTest +{ + public MongoTestServer createServer(int port, int max, int scavenge) + { + MongoTestServer server = new MongoTestServer(port,max,scavenge); + + return server; + } + + /** + * @throws Exception + */ + @Test + public void testStopSessionManagerDeleteSession() throws Exception + { + String contextPath = ""; + String servletMapping = "/server"; + + MongoTestServer server = createServer(0, 1, 0); + ServletContextHandler context = server.addContext(contextPath); + ServletHolder holder = new ServletHolder(); + TestServlet servlet = new TestServlet(); + holder.setServlet(servlet); + + context.addServlet(holder, servletMapping); + + MongoSessionManager sessionManager = (MongoSessionManager)context.getSessionHandler().getSessionManager(); + sessionManager.setPreserveOnStop(false); + MongoSessionIdManager idManager = (MongoSessionIdManager)server.getServer().getSessionIdManager(); + idManager.setPurge(true); + + + server.start(); + int port=server.getPort(); + try + { + HttpClient client = new HttpClient(); + client.start(); + try + { + //Create a session + ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=create"); + assertEquals(HttpServletResponse.SC_OK,response.getStatus()); + String sessionCookie = response.getHeaders().getStringField("Set-Cookie"); + assertTrue(sessionCookie != null); + // Mangle the cookie, replacing Path with $Path, etc. + sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path="); + + //stop the session manager + sessionManager.stop(); + + //check the database to see that the session has been marked invalid + servlet.checkSessionInDB(false); + + } + finally + { + client.stop(); + } + } + finally + { + server.stop(); + } + } + + + public static class TestServlet extends HttpServlet + { + DBCollection _sessions; + String _id; + + public TestServlet() throws UnknownHostException, MongoException + { + super(); + _sessions = new Mongo().getDB("HttpSessions").getCollection("sessions"); + } + + public void checkSessionInDB (boolean expectedValid) + { + DBObject dbSession = _sessions.findOne(new BasicDBObject("id", _id)); + assertTrue(dbSession != null); + assertEquals(expectedValid, dbSession.get("valid")); + if (!expectedValid) + assertNotNull(dbSession.get(MongoSessionManager.__INVALIDATED)); + } + + public String getId() + { + return _id; + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + String action = request.getParameter("action"); + if ("create".equals(action)) + { + HttpSession session = request.getSession(true); + session.setAttribute("foo", "bar"); + assertTrue(session.isNew()); + _id = session.getId(); + } + else if ("test".equals(action)) + { + String id = request.getRequestedSessionId(); + assertNotNull(id); + id = id.substring(0, id.indexOf(".")); + + HttpSession existingSession = request.getSession(false); + assertTrue(existingSession == null); + + //not in db any more + DBObject dbSession = _sessions.findOne(new BasicDBObject("id", id)); + assertTrue(dbSession == null); + } + } + } +} diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/StopSessionManagerPreserveSessionTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/StopSessionManagerPreserveSessionTest.java new file mode 100644 index 00000000000..515f69f9194 --- /dev/null +++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/StopSessionManagerPreserveSessionTest.java @@ -0,0 +1,98 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + + +package org.eclipse.jetty.nosql.mongodb; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.net.UnknownHostException; + +import org.eclipse.jetty.server.session.AbstractStopSessionManagerPreserveSessionTest; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.junit.Before; +import org.junit.Test; + +import com.mongodb.BasicDBObject; +import com.mongodb.DBCollection; +import com.mongodb.DBObject; +import com.mongodb.Mongo; +import com.mongodb.MongoException; + +/** + * StopSessionManagerPreserveSessionTest + * + * + */ +public class StopSessionManagerPreserveSessionTest extends AbstractStopSessionManagerPreserveSessionTest +{ + DBCollection _sessions; + + @Before + public void setUp() throws UnknownHostException, MongoException + { + _sessions = new Mongo().getDB("HttpSessions").getCollection("sessions"); + } + + + + public MongoTestServer createServer(int port) + { + MongoTestServer server = new MongoTestServer(port); + server.getServer().setStopTimeout(0); + return server; + } + + + + @Override + public void checkSessionPersisted(boolean expected) + { + DBObject dbSession = _sessions.findOne(new BasicDBObject("id", _id)); + + if (expected) + { + assertTrue(dbSession != null); + assertEquals(expected, dbSession.get("valid")); + } + else + { + assertTrue(dbSession==null); + } + } + + + @Override + public void configureSessionManagement(ServletContextHandler context) + { + ((MongoSessionManager)context.getSessionHandler().getSessionManager()).setPreserveOnStop(true); + } + + /** + * @throws Exception + */ + @Test + public void testStopSessionManagerPreserveSession() throws Exception + { + super.testStopSessionManagerPreserveSession(); + } + + + +} diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionInvalidateAndCreateTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionInvalidateAndCreateTest.java index 69acc1b1471..e42876d6181 100644 --- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionInvalidateAndCreateTest.java +++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSessionInvalidateAndCreateTest.java @@ -20,8 +20,10 @@ package org.eclipse.jetty.server.session; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.IOException; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; @@ -40,6 +42,7 @@ import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.Assert; import org.junit.Test; /** @@ -78,7 +81,7 @@ public abstract class AbstractSessionInvalidateAndCreateTest { try { - Thread.sleep(scavengePeriod * 2500L); + Thread.sleep(scavengePeriod * 3000L); } catch (InterruptedException e) { @@ -128,7 +131,7 @@ public abstract class AbstractSessionInvalidateAndCreateTest ContentResponse response2 = request2.send(); assertEquals(HttpServletResponse.SC_OK,response2.getStatus()); - // Wait for the scavenger to run, waiting 2.5 times the scavenger period + // Wait for the scavenger to run, waiting 3 times the scavenger period pause(scavengePeriod); //test that the session created in the last test is scavenged: @@ -152,6 +155,20 @@ public abstract class AbstractSessionInvalidateAndCreateTest public static class TestServlet extends HttpServlet { private boolean unbound = false; + + public class MySessionBindingListener implements HttpSessionBindingListener, Serializable + { + + public void valueUnbound(HttpSessionBindingEvent event) + { + unbound = true; + } + + public void valueBound(HttpSessionBindingEvent event) + { + + } + } @Override protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException @@ -167,25 +184,16 @@ public abstract class AbstractSessionInvalidateAndCreateTest HttpSession session = request.getSession(false); if (session != null) { + //invalidate existing session session.invalidate(); //now make a new session session = request.getSession(true); session.setAttribute("identity", "session2"); - session.setAttribute("listener", new HttpSessionBindingListener() - { - - public void valueUnbound(HttpSessionBindingEvent event) - { - unbound = true; - } - - public void valueBound(HttpSessionBindingEvent event) - { - - } - }); + session.setAttribute("listener", new MySessionBindingListener()); } + else + fail("Session already missing"); } } } diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractStopSessionManagerPreserveSessionTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractStopSessionManagerPreserveSessionTest.java new file mode 100644 index 00000000000..c75b853ee91 --- /dev/null +++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractStopSessionManagerPreserveSessionTest.java @@ -0,0 +1,116 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.session; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.Test; + +public abstract class AbstractStopSessionManagerPreserveSessionTest +{ + public String _id; + + + public abstract void checkSessionPersisted (boolean expected); + + public abstract AbstractTestServer createServer (int port); + + public abstract void configureSessionManagement(ServletContextHandler context); + + @Test + public void testStopSessionManagerPreserveSession() throws Exception + { + String contextPath = ""; + String servletMapping = "/server"; + + AbstractTestServer server = createServer(0); + ServletContextHandler context = server.addContext(contextPath); + ServletHolder holder = new ServletHolder(); + TestServlet servlet = new TestServlet(); + holder.setServlet(servlet); + + context.addServlet(holder, servletMapping); + + configureSessionManagement(context); + + server.start(); + int port=server.getPort(); + try + { + HttpClient client = new HttpClient(); + client.start(); + try + { + //Create a session + ContentResponse response = client.GET("http://localhost:" + port + contextPath + servletMapping + "?action=create"); + assertEquals(HttpServletResponse.SC_OK,response.getStatus()); + String sessionCookie = response.getHeaders().getStringField("Set-Cookie"); + assertTrue(sessionCookie != null); + // Mangle the cookie, replacing Path with $Path, etc. + sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path="); + + //stop the session manager + context.getSessionHandler().getSessionManager().stop(); + + //check the database to see that the session is still valid + checkSessionPersisted(true); + + } + finally + { + client.stop(); + } + } + finally + { + server.stop(); + } + } + + public class TestServlet extends HttpServlet + { + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + String action = request.getParameter("action"); + if ("create".equals(action)) + { + HttpSession session = request.getSession(true); + session.setAttribute("foo", "bar"); + assertTrue(session.isNew()); + _id = session.getId(); + } + } + } +}