410750 Mongo clustered sessions persisted across stops

This commit is contained in:
Jan Bartel 2013-11-21 12:11:24 +11:00
parent e92f44ed73
commit 092c53b335
23 changed files with 1345 additions and 340 deletions

View File

@ -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);
}
}

View File

@ -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<NoSqlSession> sessions=new ArrayList<NoSqlSession>(_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<NoSqlSession>(_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?
*/
}
/* ------------------------------------------------------------ */
/**

View File

@ -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
* <b>USE WITH CAUTION</b>
*/
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<contexts.length; i++)
{
SessionHandler sessionHandler = ((ContextHandler)contexts[i]).getChildHandlerByClass(SessionHandler.class);
if (sessionHandler != null)
{
SessionManager manager = sessionHandler.getSessionManager();
if (manager != null && manager instanceof MongoSessionManager)
{
((MongoSessionManager)manager).expire(sessionId);
}
}
}
}
}
/* ------------------------------------------------------------ */
@Override

View File

@ -44,6 +44,55 @@ import com.mongodb.DBObject;
import com.mongodb.MongoException;
/**
* MongoSessionManager
*
* Clustered session manager using MongoDB as the shared DB instance.
* The document model is an outer object that contains the elements:
* <ul>
* <li>"id" : session_id </li>
* <li>"created" : create_time </li>
* <li>"accessed": last_access_time </li>
* <li>"maxIdle" : max_idle_time setting as session was created </li>
* <li>"expiry" : time at which session should expire </li>
* <li>"valid" : session_valid </li>
* <li>"context" : a nested object containing 1 nested object per context for which the session id is in use
* </ul>
* Each of the nested objects inside the "context" element contains:
* <ul>
* <li>unique_context_name : nested object containing name:value pairs of the session attributes for that context</li>
* </ul>
* <p>
* 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.
* </p>
* <p>
* For example:
* <code>
* { "_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
* }
* </code>
* </p>
* <p>
* 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"
* </p>
*/
@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();
}
/*------------------------------------------------------------ */

View File

@ -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;
}
/* ------------------------------------------------------------ */

View File

@ -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<HashedSession> sessions=new ArrayList<HashedSession>(_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

View File

@ -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<String, AbstractSession> _sessions;
private ConcurrentHashMap<String, Session> _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<String, AbstractSession>();
_sessions = new ConcurrentHashMap<String, Session>();
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<Session> sessions = (_sessions == null? new ArrayList<Session>() :new ArrayList<Session>(_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<Session>(_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();

View File

@ -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
{
}

View File

@ -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<String> getSessionIds ()
throws Exception
{

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -21,7 +21,7 @@
<parent>
<groupId>org.eclipse.jetty.tests</groupId>
<artifactId>test-sessions-parent</artifactId>
<version>9.1.0-SNAPSHOT</version>
<version>9.1.1-SNAPSHOT</version>
</parent>
<artifactId>test-mongodb-sessions</artifactId>
<name>Jetty Tests :: Sessions :: Mongo</name>

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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
}
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}
}
}

View File

@ -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();
}
}

View File

@ -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");
}
}
}

View File

@ -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();
}
}
}
}