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;