From 7b0a0e05f97ae73b47d5f668c7c40ba20d67c127 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Fri, 6 Mar 2015 12:40:38 +1100 Subject: Add in idle expiry of sessions silently in the cache; modify the LightLoadTest so that it is clear it is NOT testing expected behaviour of any jetty session impl; add better load test for a single node. --- .../infinispan/InfinispanSessionIdManager.java | 85 +++++++- .../infinispan/InfinispanSessionManager.java | 236 ++++++++++---------- .../jetty/server/session/LightLoadTest.java | 40 ---- .../jetty/server/session/ScatterGunLoadTest.java | 40 ++++ .../session/InfinispanTestSessionServer.java | 24 +- .../jetty/server/session/SameNodeLoadTest.java | 66 ++++++ .../session/remote/RemoteSameNodeLoadTest.java | 69 ++++++ .../eclipse/jetty/nosql/mongodb/LightLoadTest.java | 43 ---- .../jetty/nosql/mongodb/ScatterGunLoadTest.java | 43 ++++ .../server/session/AbstractLightLoadTest.java | 242 --------------------- .../server/session/AbstractSameNodeLoadTest.java | 231 ++++++++++++++++++++ .../server/session/AbstractScatterGunLoadTest.java | 242 +++++++++++++++++++++ ...tractStopSessionManagerPreserveSessionTest.java | 5 + 13 files changed, 905 insertions(+), 461 deletions(-) delete mode 100644 tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/LightLoadTest.java create mode 100644 tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/ScatterGunLoadTest.java create mode 100644 tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/SameNodeLoadTest.java create mode 100644 tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/remote/RemoteSameNodeLoadTest.java delete mode 100644 tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/LightLoadTest.java create mode 100644 tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/ScatterGunLoadTest.java delete mode 100644 tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractLightLoadTest.java create mode 100644 tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSameNodeLoadTest.java create mode 100644 tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractScatterGunLoadTest.java diff --git a/jetty-infinispan/src/main/java/org/eclipse/jetty/session/infinispan/InfinispanSessionIdManager.java b/jetty-infinispan/src/main/java/org/eclipse/jetty/session/infinispan/InfinispanSessionIdManager.java index 257f6ba3d0..7ca6a58b73 100644 --- a/jetty-infinispan/src/main/java/org/eclipse/jetty/session/infinispan/InfinispanSessionIdManager.java +++ b/jetty-infinispan/src/main/java/org/eclipse/jetty/session/infinispan/InfinispanSessionIdManager.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.session.infinispan; import java.util.Random; +import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @@ -52,18 +53,25 @@ import org.infinispan.commons.api.BasicCache; * * where [id] is the id of the session. * + * If the first session to be added is not immortal (ie it has a timeout on it) then + * the corresponding session id is entered into infinispan with an idle expiry timeout + * equivalent to double the session's timeout (the multiplier is configurable). + * + * * Having one entry per in-use session id means that there is no contention on * cache entries (as would be the case if a single entry was kept containing a * list of in-use session ids). * - * TODO synchronization + * */ public class InfinispanSessionIdManager extends AbstractSessionIdManager { private final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session"); - protected final static String ID_KEY = "__o.e.j.s.infinispanIdMgr__"; + public final static String ID_KEY = "__o.e.j.s.infinispanIdMgr__"; + public static final int DEFAULT_IDLE_EXPIRY_MULTIPLE = 2; protected BasicCache _cache; private Server _server; + private int _idleExpiryMultiple = DEFAULT_IDLE_EXPIRY_MULTIPLE; @@ -132,7 +140,8 @@ public class InfinispanSessionIdManager extends AbstractSessionIdManager String clusterId = getClusterId(id); - //ask the cluster + //ask the cluster - this should also tickle the idle expiration timer on the sessionid entry + //keeping it valid try { return exists(clusterId); @@ -155,13 +164,35 @@ public class InfinispanSessionIdManager extends AbstractSessionIdManager @Override public void addSession(HttpSession session) { - if (session == null) - return; + if (session == null) + return; + + //insert into the cache and set an idle expiry on the entry that + //is based off the max idle time configured for the session. If the + //session is immortal, then there is no idle expiry on the corresponding + //session id + if (session.getMaxInactiveInterval() == 0) + insert (((AbstractSession)session).getClusterId()); + else + insert (((AbstractSession)session).getClusterId(), session.getMaxInactiveInterval() * getIdleExpiryMultiple()); + } + - //insert into the cache - insert (((AbstractSession)session).getClusterId()); + public void setIdleExpiryMultiple (int multiplier) + { + if (multiplier <= 1) + { + LOG.warn("Idle expiry multiple of {} for session ids set to less than minimum. Using value of {} instead.", multiplier, DEFAULT_IDLE_EXPIRY_MULTIPLE); + } + _idleExpiryMultiple = multiplier; } + public int getIdleExpiryMultiple () + { + return _idleExpiryMultiple; + } + + /** * Remove a session id from the list of in-use ids. * @@ -246,16 +277,38 @@ public class InfinispanSessionIdManager extends AbstractSessionIdManager } + /** + * Get the cache. + * @return + */ public BasicCache getCache() { return _cache; } + /** + * Set the cache. + * @param cache + */ public void setCache(BasicCache cache) { this._cache = cache; } + + + /** + * Do any operation to the session id in the cache to + * ensure its idle expiry time moves forward + * @param id + */ + public void touch (String id) + { + exists(id); + } + + + /** * Ask the cluster if a particular id exists. * @@ -267,10 +320,7 @@ public class InfinispanSessionIdManager extends AbstractSessionIdManager if (_cache == null) throw new IllegalStateException ("No cache"); - Object key =_cache.get(makeKey(id)); - if (key == null) - return false; - return true; + return _cache.containsKey(makeKey(id)); } @@ -288,6 +338,19 @@ public class InfinispanSessionIdManager extends AbstractSessionIdManager } + /** + * Put a session id into the cluster with an idle expiry. + * + * @param id + */ + protected void insert (String id, long idleTimeOutSec) + { + if (_cache == null) + throw new IllegalStateException ("No cache"); + + _cache.putIfAbsent(makeKey(id),id,-1L, TimeUnit.SECONDS, idleTimeOutSec, TimeUnit.SECONDS); + } + /** * Remove a session id from the cluster. diff --git a/jetty-infinispan/src/main/java/org/eclipse/jetty/session/infinispan/InfinispanSessionManager.java b/jetty-infinispan/src/main/java/org/eclipse/jetty/session/infinispan/InfinispanSessionManager.java index 45778f3f33..6e79a2a463 100644 --- a/jetty-infinispan/src/main/java/org/eclipse/jetty/session/infinispan/InfinispanSessionManager.java +++ b/jetty-infinispan/src/main/java/org/eclipse/jetty/session/infinispan/InfinispanSessionManager.java @@ -207,102 +207,6 @@ public class InfinispanSessionManager extends AbstractSessionManager - /** - * SerializableSession - * - * Helper class that is responsible for de/serialization of the non-serializable session object. - */ - public class SerializableSession implements Serializable - { - - /** - * - */ - private static final long serialVersionUID = -7603529353470249059L; - private transient Session _session; - - - public SerializableSession () - { - - } - - public SerializableSession (Session session) - { - setSession(session); - } - - /** - * Existing session - * @param session - */ - public void setSession (Session session) - { - _session = session; - } - - public Session getSession () - { - return _session; - } - - - private void writeObject(java.io.ObjectOutputStream out) throws IOException - { - if (_session == null) - throw new IOException ("No session to serialize"); - - out.writeUTF(_session.getClusterId()); //session id - out.writeUTF(_session.getContextPath()); //context path - out.writeUTF(_session.getVHost()); //first vhost - - out.writeLong(_session.getAccessed());//accessTime - out.writeLong(_session.getLastAccessedTime()); //lastAccessTime - out.writeLong(_session.getCreationTime()); //time created - out.writeLong(_session.getCookieSetTime());//time cookie was set - out.writeUTF(_session.getLastNode()); //name of last node managing - - out.writeLong(_session.getExpiry()); - out.writeLong(_session.getMaxInactiveInterval()); - out.writeObject(_session.getAttributeMap()); - } - - - private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException - { - String clusterId = in.readUTF(); - String context = in.readUTF(); - String vhost = in.readUTF(); - - Long accessed = in.readLong();//accessTime - Long lastAccessed = in.readLong(); //lastAccessTime - Long created = in.readLong(); //time created - Long cookieSet = in.readLong();//time cookie was set - String lastNode = in.readUTF(); //last managing node - Long expiry = in.readLong(); - Long maxIdle = in.readLong(); - HashMap attributes = (HashMap)in.readObject(); - Session session = new Session(clusterId, created, accessed, maxIdle); - session.setCookieSetTime(cookieSet); - session.setLastAccessedTime(lastAccessed); - session.setLastNode(lastNode); - session.setContextPath(context); - session.setVHost(vhost); - session.setExpiry(expiry); - session.addAttributes(attributes); - setSession(session); - } - - - private void readObjectNoData() throws ObjectStreamException - { - setSession(null); - } - - - } - - /** * Session * @@ -338,12 +242,24 @@ public class InfinispanSessionManager extends AbstractSessionManager private String _lastNode; + /** + * If dirty, session needs to be (re)sent to cluster + */ + protected boolean _dirty=false; + + /** * Any virtual hosts for the context with which this session is associated */ private String _vhost; + + + /** + * Count of how many threads are active in this session + */ + private AtomicInteger _activeThreads = new AtomicInteger(0); @@ -361,6 +277,7 @@ public class InfinispanSessionManager extends AbstractSessionManager _lastNode = getSessionIdManager().getWorkerName(); setVHost(InfinispanSessionManager.getVirtualHost(_context)); setContextPath(InfinispanSessionManager.getContextPath(_context)); + _activeThreads.incrementAndGet(); //access will not be called on a freshly created session so increment here } @@ -403,14 +320,18 @@ public class InfinispanSessionManager extends AbstractSessionManager { long now = System.currentTimeMillis(); + //lock so that no other thread can call access or complete until the first one has refreshed the session object if necessary _lock.lock(); - - //if the first thread, check that the session in memory is not stale, if we're checking for stale sessions - if (getStaleIntervalSec() > 0 && (now - getLastSyncTime()) >= (getStaleIntervalSec() * 1000L)) + //a request thread is entering + if (_activeThreads.incrementAndGet() == 1) { - if (LOG.isDebugEnabled()) - LOG.debug("Acess session({}) for context {} on worker {} stale session. Reloading.", getId(), getContextPath(), getSessionIdManager().getWorkerName()); - refresh(); + //if the first thread, check that the session in memory is not stale, if we're checking for stale sessions + if (getStaleIntervalSec() > 0 && (now - getLastSyncTime()) >= (getStaleIntervalSec() * 1000L)) + { + if (LOG.isDebugEnabled()) + LOG.debug("Acess session({}) for context {} on worker {} stale session. Reloading.", getId(), getContextPath(), getSessionIdManager().getWorkerName()); + refresh(); + } } } catch (Exception e) @@ -441,32 +362,65 @@ public class InfinispanSessionManager extends AbstractSessionManager { super.complete(); + //lock so that no other thread that might be calling access can proceed until this complete is done + _lock.lock(); + try { - //an invalid session will already have been removed from the - //local session map and deleted from the cluster. If its valid save - //it to the cluster. - //TODO consider doing only periodic saves if only the last access - //time to the session changes - if (isValid()) + //if this is the last request thread to be in the session + if (_activeThreads.decrementAndGet() == 0) { - willPassivate(); try { - _lock.lock(); - save(this); + //an invalid session will already have been removed from the + //local session map and deleted from the cluster. If its valid save + //it to the cluster. + //TODO consider doing only periodic saves if only the last access + //time to the session changes + if (isValid()) + { + //if session still valid && its dirty or stale or never been synced, write it to the cluster + //otherwise, we just keep the updated last access time in memory + if (_dirty || getLastSyncTime() == 0 || isStale(System.currentTimeMillis())) + { + willPassivate(); + save(this); + didActivate(); + } + } } + catch (Exception e) + { + LOG.warn("Problem saving session({})",getId(), e); + } finally { - _lock.unlock(); + _dirty = false; } - didActivate(); } } - catch (Exception e) + finally { - LOG.warn("Problem saving session({})",getId(), e); - } + _lock.unlock(); + } + } + + /** Test if the session is stale + * @param atTime + * @return + */ + protected boolean isStale (long atTime) + { + return (getStaleIntervalSec() > 0) && (atTime - getLastSyncTime() >= (getStaleIntervalSec()*1000L)); + } + + + /** Test if the session is dirty + * @return + */ + protected boolean isDirty () + { + return _dirty; } /** @@ -480,6 +434,8 @@ public class InfinispanSessionManager extends AbstractSessionManager super.timeout(); } + + /** * Reload the session from the cluster. If the node that * last managed the session from the cluster is ourself, @@ -561,6 +517,7 @@ public class InfinispanSessionManager extends AbstractSessionManager public void swapId (String newId, String newNodeId) { + //TODO probably synchronize rather than use the access/complete lock? _lock.lock(); setClusterId(newId); setNodeId(newNodeId); @@ -574,7 +531,7 @@ public class InfinispanSessionManager extends AbstractSessionManager if (value == null && old == null) return; //if same as remove attribute but attribute was already removed, no change - //TODO _dirty = true; + _dirty = true; } @@ -716,7 +673,8 @@ public class InfinispanSessionManager extends AbstractSessionManager if (candidateSession != null) { //double check the state of the session in the cache, as the - //session may have migrated to another node + //session may have migrated to another node. This leaves a window + //where the cached session may have been changed by another node Session cachedSession = load(makeKey(candidateId, _context)); if (cachedSession == null) { @@ -752,8 +710,7 @@ public class InfinispanSessionManager extends AbstractSessionManager /** - * Set the interval between runs of the scavenger. As this will be a costly - * exercise (need to iterate over all cache entries) it should not be run too + * Set the interval between runs of the scavenger. It should not be run too * often. * * @@ -951,8 +908,28 @@ public class InfinispanSessionManager extends AbstractSessionManager @Override protected void shutdownSessions() throws Exception { - //TODO if implementing period saves, if we might have un-saved changes, - //then we need to write them back to the clustered cache + Set keys = new HashSet(_sessions.keySet()); + for (String key:keys) + { + Session session = _sessions.remove(key); //take the session out of the session list + //If the session is dirty, then write it to the cluster. + //If the session is simply stale do NOT write it to the cluster, as some other node + //may have started managing that session - this means that the last accessed/expiry time + //will not be updated, meaning it may look like it can expire sooner than it should. + try + { + if (session.isDirty()) + { + if (LOG.isDebugEnabled()) + LOG.debug("Saving dirty session {} before exiting ", session.getId()); + save(session); + } + } + catch (Exception e) + { + LOG.warn(e); + } + } } @@ -1057,7 +1034,20 @@ public class InfinispanSessionManager extends AbstractSessionManager if (LOG.isDebugEnabled()) LOG.debug("Writing session {} to cluster", session.getId()); SerializableSessionData storableSession = new SerializableSessionData(session); - _cache.put(makeKey(session, _context), storableSession); + + //Put an idle timeout on the cache entry if the session is not immortal - + //if no requests arrive at any node before this timeout occurs, or no node + //scavenges the session before this timeout occurs, the session will be removed. + //NOTE: that no session listeners can be called for this. + InfinispanSessionIdManager sessionIdManager = (InfinispanSessionIdManager)getSessionIdManager(); + if (storableSession.maxInactive > 0) + _cache.put(makeKey(session, _context), storableSession, -1, TimeUnit.SECONDS, storableSession.maxInactive*sessionIdManager.getIdleExpiryMultiple(), TimeUnit.SECONDS); + else + _cache.put(makeKey(session, _context), storableSession); + + //tickle the session id manager to keep the sessionid entry for this session up-to-date + sessionIdManager.touch(session.getClusterId()); + session.setLastSyncTime(System.currentTimeMillis()); } diff --git a/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/LightLoadTest.java b/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/LightLoadTest.java deleted file mode 100644 index acff35611b..0000000000 --- a/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/LightLoadTest.java +++ /dev/null @@ -1,40 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2015 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; - -/** - * LightLoadTest - */ -public class LightLoadTest extends AbstractLightLoadTest -{ - - public AbstractTestServer createServer(int port) - { - return new HashTestServer(port); - } - - @Test - public void testLightLoad() throws Exception - { - super.testLightLoad(); - } - -} diff --git a/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/ScatterGunLoadTest.java b/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/ScatterGunLoadTest.java new file mode 100644 index 0000000000..90e428cf0f --- /dev/null +++ b/tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/ScatterGunLoadTest.java @@ -0,0 +1,40 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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; + +/** + * ScatterGunLoadTest + */ +public class ScatterGunLoadTest extends AbstractScatterGunLoadTest +{ + + public AbstractTestServer createServer(int port) + { + return new HashTestServer(port); + } + + @Test + public void testLightLoad() throws Exception + { + super.testLightLoad(); + } + +} diff --git a/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/InfinispanTestSessionServer.java b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/InfinispanTestSessionServer.java index 8d3479c4da..c6b21229a5 100644 --- a/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/InfinispanTestSessionServer.java +++ b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/InfinispanTestSessionServer.java @@ -76,14 +76,34 @@ public class InfinispanTestSessionServer extends AbstractTestServer return new SessionHandler(sessionManager); } + public boolean exists (String id) + { + BasicCache cache = ((InfinispanSessionIdManager)_sessionIdManager).getCache(); + if (cache != null) + { + return cache.containsKey(id); + } + + return false; + } + + public Object get (String id) + { + BasicCache cache = ((InfinispanSessionIdManager)_sessionIdManager).getCache(); + if (cache != null) + { + return cache.get(id); + } + + return null; + } public void dumpCache () { BasicCache cache = ((InfinispanSessionIdManager)_sessionIdManager).getCache(); if (cache != null) { - System.err.println(cache.getName()+" contains "+cache.size()+" entries"); - + System.err.println(cache.getName()+" contains "+cache.size()+" entries"); } } diff --git a/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/SameNodeLoadTest.java b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/SameNodeLoadTest.java new file mode 100644 index 0000000000..44a9c1785c --- /dev/null +++ b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/SameNodeLoadTest.java @@ -0,0 +1,66 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.AfterClass; +import org.junit.BeforeClass; + +/** + * SameNodeLoadTest + * + * + */ +public class SameNodeLoadTest extends AbstractSameNodeLoadTest +{ + + public static InfinispanTestSupport __testSupport; + + + + @BeforeClass + public static void setup () throws Exception + { + __testSupport = new InfinispanTestSupport(); + __testSupport.setup(); + } + + @AfterClass + public static void teardown () throws Exception + { + __testSupport.teardown(); + } + + /** + * @see org.eclipse.jetty.server.session.AbstractSameNodeLoadTest#createServer(int) + */ + @Override + public AbstractTestServer createServer(int port) + { + InfinispanTestSessionServer server = new InfinispanTestSessionServer(port, __testSupport.getCache()); + return server; + } + + @Override + public void testLoad() throws Exception + { + super.testLoad(); + } + +} diff --git a/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/remote/RemoteSameNodeLoadTest.java b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/remote/RemoteSameNodeLoadTest.java new file mode 100644 index 0000000000..ea98f0f8b6 --- /dev/null +++ b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/remote/RemoteSameNodeLoadTest.java @@ -0,0 +1,69 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.remote; + +import org.eclipse.jetty.server.session.AbstractSameNodeLoadTest; +import org.eclipse.jetty.server.session.AbstractTestServer; +import org.eclipse.jetty.server.session.InfinispanTestSessionServer; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +/** + * SameNodeLoadTest + * + * + */ +public class RemoteSameNodeLoadTest extends AbstractSameNodeLoadTest +{ + + public static RemoteInfinispanTestSupport __testSupport; + + + + @BeforeClass + public static void setup () throws Exception + { + __testSupport = new RemoteInfinispanTestSupport("remote-session-test"); + __testSupport.setup(); + } + + @AfterClass + public static void teardown () throws Exception + { + __testSupport.teardown(); + } + + /** + * @see org.eclipse.jetty.server.session.AbstractSameNodeLoadTest#createServer(int) + */ + @Override + public AbstractTestServer createServer(int port) + { + InfinispanTestSessionServer server = new InfinispanTestSessionServer(port, __testSupport.getCache()); + return server; + } + + @Override + public void testLoad() throws Exception + { + super.testLoad(); + } + +} diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/LightLoadTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/LightLoadTest.java deleted file mode 100644 index 9d241e00db..0000000000 --- a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/LightLoadTest.java +++ /dev/null @@ -1,43 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2015 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.AbstractLightLoadTest; -import org.eclipse.jetty.server.session.AbstractTestServer; -import org.junit.Ignore; -import org.junit.Test; - -/** - * LightLoadTest - */ -public class LightLoadTest extends AbstractLightLoadTest -{ - - public AbstractTestServer createServer(int port) - { - return new MongoTestServer(port); - } - - @Test - public void testLightLoad() throws Exception - { - super.testLightLoad(); - } - -} diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/ScatterGunLoadTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/ScatterGunLoadTest.java new file mode 100644 index 0000000000..34d7d07e5d --- /dev/null +++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/ScatterGunLoadTest.java @@ -0,0 +1,43 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.AbstractScatterGunLoadTest; +import org.eclipse.jetty.server.session.AbstractTestServer; +import org.junit.Ignore; +import org.junit.Test; + +/** + * ScatterGunLoadTest + */ +public class ScatterGunLoadTest extends AbstractScatterGunLoadTest +{ + + public AbstractTestServer createServer(int port) + { + return new MongoTestServer(port); + } + + @Test + public void testLightLoad() throws Exception + { + super.testLightLoad(); + } + +} diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractLightLoadTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractLightLoadTest.java deleted file mode 100644 index d9b798b700..0000000000 --- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractLightLoadTest.java +++ /dev/null @@ -1,242 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2015 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.assertTrue; - -import java.io.IOException; -import java.io.PrintWriter; -import java.util.Random; -import java.util.concurrent.CyclicBarrier; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -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.client.api.Request; -import org.junit.Test; - - -/** - * AbstractLightLoadTest - * - * This is an unrealistic test. It takes a scatter-gun approach to smearing a - * single session across 2 different nodes at once. - * - * In the real world, we must have a load balancer that uses sticky sessions - * to keep the session pinned to a particular node. - */ -public abstract class AbstractLightLoadTest -{ - protected boolean _stress = Boolean.getBoolean( "STRESS" ); - - public abstract AbstractTestServer createServer(int port); - - @Test - public void testLightLoad() - throws Exception - { - if ( _stress ) - { - String contextPath = ""; - String servletMapping = "/server"; - AbstractTestServer server1 = createServer( 0 ); - server1.addContext( contextPath ).addServlet( TestServlet.class, servletMapping ); - - try - { - server1.start(); - int port1 = server1.getPort(); - AbstractTestServer server2 = createServer( 0 ); - server2.addContext( contextPath ).addServlet( TestServlet.class, servletMapping ); - - try - { - server2.start(); - int port2=server2.getPort(); - HttpClient client = new HttpClient(); - client.start(); - try - { - String[] urls = new String[2]; - urls[0] = "http://localhost:" + port1 + contextPath + servletMapping; - urls[1] = "http://localhost:" + port2 + contextPath + servletMapping; - - //create session via first server - ContentResponse response1 = client.GET(urls[0] + "?action=init"); - assertEquals(HttpServletResponse.SC_OK,response1.getStatus()); - String sessionCookie = response1.getHeaders().getStringField( "Set-Cookie" ); - assertTrue(sessionCookie != null); - // Mangle the cookie, replacing Path with $Path, etc. - sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path="); - - //simulate 50 clients making 100 requests each - ExecutorService executor = Executors.newCachedThreadPool(); - int clientsCount = 50; - CyclicBarrier barrier = new CyclicBarrier( clientsCount + 1 ); - int requestsCount = 100; - Worker[] workers = new Worker[clientsCount]; - for ( int i = 0; i < clientsCount; ++i ) - { - workers[i] = new Worker( barrier, requestsCount, sessionCookie, urls ); - workers[i].start(); - executor.execute( workers[i] ); - } - // Wait for all workers to be ready - barrier.await(); - long start = System.nanoTime(); - - // Wait for all workers to be done - barrier.await(); - long end = System.nanoTime(); - long elapsed = TimeUnit.NANOSECONDS.toMillis( end - start ); - System.out.println( "elapsed ms: " + elapsed ); - - for ( Worker worker : workers ) - worker.stop(); - executor.shutdownNow(); - - // Perform one request to get the result - Request request = client.newRequest( urls[0] + "?action=result" ); - request.header("Cookie", sessionCookie); - ContentResponse response2 = request.send(); - assertEquals(HttpServletResponse.SC_OK,response2.getStatus()); - String response = response2.getContentAsString(); - System.out.println( "get = " + response ); - assertEquals(response.trim(), String.valueOf( clientsCount * requestsCount ) ); - } - finally - { - client.stop(); - } - } - finally - { - server2.stop(); - } - } - finally - { - server1.stop(); - } - } - } - - public static class Worker - implements Runnable - { - private final HttpClient client; - - private final CyclicBarrier barrier; - - private final int requestsCount; - - private final String sessionCookie; - - private final String[] urls; - - - public Worker( CyclicBarrier barrier, int requestsCount, String sessionCookie, String[] urls ) - { - this.client = new HttpClient(); - this.barrier = barrier; - this.requestsCount = requestsCount; - this.sessionCookie = sessionCookie; - this.urls = urls; - } - - public void start() - throws Exception - { - client.start(); - } - - public void stop() - throws Exception - { - client.stop(); - } - - public void run() - { - try - { - // Wait for all workers to be ready - barrier.await(); - - Random random = new Random( System.nanoTime() ); - - for ( int i = 0; i < requestsCount; ++i ) - { - int urlIndex = random.nextInt( urls.length ); - Request request = client.newRequest(urls[urlIndex] + "?action=increment"); - request.header("Cookie", sessionCookie); - ContentResponse response = request.send(); - assertEquals(HttpServletResponse.SC_OK,response.getStatus()); - } - - // Wait for all workers to be done - barrier.await(); - } - catch ( Exception x ) - { - throw new RuntimeException( x ); - } - } - } - - public static class TestServlet - extends HttpServlet - { - @Override - protected void doGet( HttpServletRequest request, HttpServletResponse response ) - throws ServletException, IOException - { - String action = request.getParameter( "action" ); - if ( "init".equals( action ) ) - { - HttpSession session = request.getSession( true ); - session.setAttribute( "value", 0 ); - } - else if ( "increment".equals( action ) ) - { - // Without synchronization - HttpSession session = request.getSession( false ); - int value = (Integer) session.getAttribute( "value" ); - session.setAttribute( "value", value + 1 ); - } - else if ( "result".equals( action ) ) - { - HttpSession session = request.getSession( false ); - int value = (Integer) session.getAttribute( "value" ); - PrintWriter writer = response.getWriter(); - writer.println( value ); - writer.flush(); - } - } - } -} diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSameNodeLoadTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSameNodeLoadTest.java new file mode 100644 index 0000000000..999858fb5b --- /dev/null +++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractSameNodeLoadTest.java @@ -0,0 +1,231 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.assertTrue; +import static org.junit.Assert.assertNotNull; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Random; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +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.client.api.Request; +import org.junit.Test; + + +/** + * AbstractSameNodeLoadTest + * + * This test performs multiple concurrent requests for the same session on the same node. + * + */ +public abstract class AbstractSameNodeLoadTest +{ + protected boolean _stress = Boolean.getBoolean( "STRESS" ); + + public abstract AbstractTestServer createServer(int port); + + @Test + public void testLoad() throws Exception + { + if ( _stress ) + { + String contextPath = ""; + String servletMapping = "/server"; + AbstractTestServer server1 = createServer( 0 ); + server1.addContext( contextPath ).addServlet( TestServlet.class, servletMapping ); + + try + { + server1.start(); + int port1 = server1.getPort(); + + HttpClient client = new HttpClient(); + client.start(); + try + { + String url = "http://localhost:" + port1 + contextPath + servletMapping; + + + //create session via first server + ContentResponse response1 = client.GET(url + "?action=init"); + assertEquals(HttpServletResponse.SC_OK,response1.getStatus()); + String sessionCookie = response1.getHeaders().getStringField( "Set-Cookie" ); + assertTrue(sessionCookie != null); + // Mangle the cookie, replacing Path with $Path, etc. + sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path="); + + //simulate 10 clients making 100 requests each + ExecutorService executor = Executors.newCachedThreadPool(); + int clientsCount = 10; + CyclicBarrier barrier = new CyclicBarrier( clientsCount + 1 ); + int requestsCount = 100; + Worker[] workers = new Worker[clientsCount]; + for ( int i = 0; i < clientsCount; ++i ) + { + workers[i] = new Worker(barrier, client, requestsCount, sessionCookie, url); + executor.execute( workers[i] ); + } + // Wait for all workers to be ready + barrier.await(); + long start = System.nanoTime(); + + // Wait for all workers to be done + barrier.await(); + long end = System.nanoTime(); + long elapsed = TimeUnit.NANOSECONDS.toMillis( end - start ); + System.out.println( "elapsed ms: " + elapsed ); + + executor.shutdownNow(); + + // Perform one request to get the result + Request request = client.newRequest( url + "?action=result" ); + request.header("Cookie", sessionCookie); + ContentResponse response2 = request.send(); + assertEquals(HttpServletResponse.SC_OK,response2.getStatus()); + String response = response2.getContentAsString(); + System.out.println( "get = " + response ); + assertEquals(response.trim(), String.valueOf( clientsCount * requestsCount ) ); + } + finally + { + client.stop(); + } + } + finally + { + server1.stop(); + } + } + } + + public static class Worker implements Runnable + { + public static int COUNT = 0; + + private final HttpClient client; + + private final CyclicBarrier barrier; + + private final int requestsCount; + + private final String sessionCookie; + + private final String url; + + private final String name; + + + public Worker(CyclicBarrier barrier, HttpClient client, int requestsCount, String sessionCookie, String url) + { + this.client = client; + this.barrier = barrier; + this.requestsCount = requestsCount; + this.sessionCookie = sessionCookie; + this.url = url; + this.name = ""+(COUNT++); + } + + + public void run() + { + try + { + // Wait for all workers to be ready + barrier.await(); + + Random random = new Random( System.nanoTime() ); + + for ( int i = 0; i < requestsCount; ++i ) + { + int pauseMsec = random.nextInt(1000); + + //wait a random number of milliseconds between requests up to 1 second + if (pauseMsec > 0) + { + Thread.currentThread().sleep(pauseMsec); + } + Request request = client.newRequest(url + "?action=increment"); + request.header("Cookie", sessionCookie); + ContentResponse response = request.send(); + assertEquals(HttpServletResponse.SC_OK,response.getStatus()); + } + + // Wait for all workers to be done + barrier.await(); + } + catch ( Exception x ) + { + throw new RuntimeException( x ); + } + } + } + + public static class TestServlet + extends HttpServlet + { + @Override + protected void doGet( HttpServletRequest request, HttpServletResponse response ) + throws ServletException, IOException + { + String action = request.getParameter( "action" ); + if ( "init".equals( action ) ) + { + HttpSession session = request.getSession( true ); + session.setAttribute( "value", 0 ); + } + else if ( "increment".equals( action ) ) + { + HttpSession session = request.getSession( false ); + assertNotNull(session); + synchronized(session) + { + int value = (Integer) session.getAttribute( "value" ); + session.setAttribute( "value", value + 1 ); + } + } + else if ( "result".equals( action ) ) + { + HttpSession session = request.getSession( false ); + assertNotNull(session); + Integer value = null; + synchronized (session) + { + value = (Integer) session.getAttribute( "value" ); + } + PrintWriter writer = response.getWriter(); + writer.println( value ); + writer.flush(); + } + } + } +} diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractScatterGunLoadTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractScatterGunLoadTest.java new file mode 100644 index 0000000000..ed24b6068a --- /dev/null +++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractScatterGunLoadTest.java @@ -0,0 +1,242 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.assertTrue; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Random; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +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.client.api.Request; +import org.junit.Test; + + +/** + * AbstractScatterGunLoadTest + * + * This is an unrealistic test. It takes a scatter-gun approach to smearing a + * single session across 2 different nodes at once. + * + * In the real world, we must have a load balancer that uses sticky sessions + * to keep the session pinned to a particular node. + */ +public abstract class AbstractScatterGunLoadTest +{ + protected boolean _stress = Boolean.getBoolean( "STRESS" ); + + public abstract AbstractTestServer createServer(int port); + + @Test + public void testLightLoad() + throws Exception + { + if ( _stress ) + { + String contextPath = ""; + String servletMapping = "/server"; + AbstractTestServer server1 = createServer( 0 ); + server1.addContext( contextPath ).addServlet( TestServlet.class, servletMapping ); + + try + { + server1.start(); + int port1 = server1.getPort(); + AbstractTestServer server2 = createServer( 0 ); + server2.addContext( contextPath ).addServlet( TestServlet.class, servletMapping ); + + try + { + server2.start(); + int port2=server2.getPort(); + HttpClient client = new HttpClient(); + client.start(); + try + { + String[] urls = new String[2]; + urls[0] = "http://localhost:" + port1 + contextPath + servletMapping; + urls[1] = "http://localhost:" + port2 + contextPath + servletMapping; + + //create session via first server + ContentResponse response1 = client.GET(urls[0] + "?action=init"); + assertEquals(HttpServletResponse.SC_OK,response1.getStatus()); + String sessionCookie = response1.getHeaders().getStringField( "Set-Cookie" ); + assertTrue(sessionCookie != null); + // Mangle the cookie, replacing Path with $Path, etc. + sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path="); + + //simulate 50 clients making 100 requests each + ExecutorService executor = Executors.newCachedThreadPool(); + int clientsCount = 50; + CyclicBarrier barrier = new CyclicBarrier( clientsCount + 1 ); + int requestsCount = 100; + Worker[] workers = new Worker[clientsCount]; + for ( int i = 0; i < clientsCount; ++i ) + { + workers[i] = new Worker( barrier, requestsCount, sessionCookie, urls ); + workers[i].start(); + executor.execute( workers[i] ); + } + // Wait for all workers to be ready + barrier.await(); + long start = System.nanoTime(); + + // Wait for all workers to be done + barrier.await(); + long end = System.nanoTime(); + long elapsed = TimeUnit.NANOSECONDS.toMillis( end - start ); + System.out.println( "elapsed ms: " + elapsed ); + + for ( Worker worker : workers ) + worker.stop(); + executor.shutdownNow(); + + // Perform one request to get the result + Request request = client.newRequest( urls[0] + "?action=result" ); + request.header("Cookie", sessionCookie); + ContentResponse response2 = request.send(); + assertEquals(HttpServletResponse.SC_OK,response2.getStatus()); + String response = response2.getContentAsString(); + System.out.println( "get = " + response ); + assertEquals(response.trim(), String.valueOf( clientsCount * requestsCount ) ); + } + finally + { + client.stop(); + } + } + finally + { + server2.stop(); + } + } + finally + { + server1.stop(); + } + } + } + + public static class Worker + implements Runnable + { + private final HttpClient client; + + private final CyclicBarrier barrier; + + private final int requestsCount; + + private final String sessionCookie; + + private final String[] urls; + + + public Worker( CyclicBarrier barrier, int requestsCount, String sessionCookie, String[] urls ) + { + this.client = new HttpClient(); + this.barrier = barrier; + this.requestsCount = requestsCount; + this.sessionCookie = sessionCookie; + this.urls = urls; + } + + public void start() + throws Exception + { + client.start(); + } + + public void stop() + throws Exception + { + client.stop(); + } + + public void run() + { + try + { + // Wait for all workers to be ready + barrier.await(); + + Random random = new Random( System.nanoTime() ); + + for ( int i = 0; i < requestsCount; ++i ) + { + int urlIndex = random.nextInt( urls.length ); + Request request = client.newRequest(urls[urlIndex] + "?action=increment"); + request.header("Cookie", sessionCookie); + ContentResponse response = request.send(); + assertEquals(HttpServletResponse.SC_OK,response.getStatus()); + } + + // Wait for all workers to be done + barrier.await(); + } + catch ( Exception x ) + { + throw new RuntimeException( x ); + } + } + } + + public static class TestServlet + extends HttpServlet + { + @Override + protected void doGet( HttpServletRequest request, HttpServletResponse response ) + throws ServletException, IOException + { + String action = request.getParameter( "action" ); + if ( "init".equals( action ) ) + { + HttpSession session = request.getSession( true ); + session.setAttribute( "value", 0 ); + } + else if ( "increment".equals( action ) ) + { + // Without synchronization + HttpSession session = request.getSession( false ); + int value = (Integer) session.getAttribute( "value" ); + session.setAttribute( "value", value + 1 ); + } + else if ( "result".equals( action ) ) + { + HttpSession session = request.getSession( false ); + int value = (Integer) session.getAttribute( "value" ); + PrintWriter writer = response.getWriter(); + writer.println( value ); + writer.flush(); + } + } + } +} 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 index de4c44d9f2..b67f4d7ac3 100644 --- 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 @@ -36,6 +36,11 @@ import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.junit.Test; +/** + * AbstractStopSessionManagerPreserveSessionTest + * + * + */ public abstract class AbstractStopSessionManagerPreserveSessionTest { public String _id; -- cgit v1.2.3