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.
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 257f6ba..7ca6a58 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 @@ * </pre> * 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<String,Object> _cache; private Server _server; + private int _idleExpiryMultiple = DEFAULT_IDLE_EXPIRY_MULTIPLE; @@ -132,7 +140,8 @@ 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 @@ @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 @@ } + /** + * Get the cache. + * @return + */ public BasicCache<String,Object> getCache() { return _cache; } + /** + * Set the cache. + * @param cache + */ public void setCache(BasicCache<String,Object> 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 @@ 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 @@ } + /** + * 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 45778f3..6e79a2a 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
@@ -208,102 +208,6 @@ /** - * 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<String,Object> attributes = (HashMap<String,Object>)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 * * Representation of a session in local memory. @@ -338,12 +242,24 @@ 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 @@ _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 @@ { 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 @@ { 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 @@ 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 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 @@ 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 @@ 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 @@ /** - * 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 @@ @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<String> keys = new HashSet<String>(_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 @@ 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/ScatterGunLoadTest.java similarity index 92% rename from tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/LightLoadTest.java rename to tests/test-sessions/test-hash-sessions/src/test/java/org/eclipse/jetty/server/session/ScatterGunLoadTest.java index acff356..90e428c 100644 --- 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/ScatterGunLoadTest.java
@@ -21,9 +21,9 @@ import org.junit.Test; /** - * LightLoadTest + * ScatterGunLoadTest */ -public class LightLoadTest extends AbstractLightLoadTest +public class ScatterGunLoadTest extends AbstractScatterGunLoadTest { public AbstractTestServer createServer(int port)
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 8d3479c..c6b2122 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 @@ 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 0000000..44a9c17 --- /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 0000000..ea98f0f --- /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/ScatterGunLoadTest.java similarity index 88% rename from tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/LightLoadTest.java rename to tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/ScatterGunLoadTest.java index 9d241e0..34d7d07 100644 --- 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/ScatterGunLoadTest.java
@@ -18,15 +18,15 @@ package org.eclipse.jetty.nosql.mongodb; -import org.eclipse.jetty.server.session.AbstractLightLoadTest; +import org.eclipse.jetty.server.session.AbstractScatterGunLoadTest; import org.eclipse.jetty.server.session.AbstractTestServer; import org.junit.Ignore; import org.junit.Test; /** - * LightLoadTest + * ScatterGunLoadTest */ -public class LightLoadTest extends AbstractLightLoadTest +public class ScatterGunLoadTest extends AbstractScatterGunLoadTest { public AbstractTestServer createServer(int port)
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 0000000..999858f --- /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/AbstractLightLoadTest.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractScatterGunLoadTest.java similarity index 98% rename from tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractLightLoadTest.java rename to tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/AbstractScatterGunLoadTest.java index d9b798b..ed24b60 100644 --- 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/AbstractScatterGunLoadTest.java
@@ -42,7 +42,7 @@ /** - * AbstractLightLoadTest + * AbstractScatterGunLoadTest * * This is an unrealistic test. It takes a scatter-gun approach to smearing a * single session across 2 different nodes at once. @@ -50,7 +50,7 @@ * 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 +public abstract class AbstractScatterGunLoadTest { protected boolean _stress = Boolean.getBoolean( "STRESS" );
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 de4c44d..b67f4d7 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.ServletHolder; import org.junit.Test; +/** + * AbstractStopSessionManagerPreserveSessionTest + * + * + */ public abstract class AbstractStopSessionManagerPreserveSessionTest { public String _id;