This PATCH implements the earlier PROPOSAL. namely;
> PROPOSAL: When Session Max reached, throw out oldest session > > Currently tomcat ships with the maximum number of sessions set to > unlimited. They can expire by default after 30 minutes, however if > too many sessions are created within the 30 minutes, you can run out > of memory. > > To prevent running out of memory, you might choose to limit the > allowed number of active sessions. If you use the default > StanardManager (session manager) you can set the "maxActiveSessions" > to effect a limit. However if you exceed the number of allowed > sessions, a RuntimeException (IllegalStateException) is thrown. > > I propose two changes to reduce seeing these (IllegalStateException or > OutOfMemory) exceptions for sessions; > > 1. When the maximum number of sessions is reached, change > StandardManager from throwing an IllegalStateException exception, to > expiring the Least Recently Used (LRUMap) session. > > 2. Instead of defaulting to an unlimited number of sessions (and > getting visits from OutOfMemory), limit the number of sessions to > 10000 by default. This PATCH does 1 and 2. It also fixes a problem with recycling sessions (manager was set to null before session could be recycled.) Can someone please review this? Cheers, -bob Index: ManagerBase.java =================================================================== RCS file: /home/cvspublic/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/session/ManagerBase.java,v retrieving revision 1.11 diff -u -r1.11 ManagerBase.java --- ManagerBase.java 14 Jan 2002 23:38:03 -0000 1.11 +++ ManagerBase.java 18 Jun 2002 20:47:04 -0000 @@ -73,6 +73,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Random; +import java.util.Map; +import java.util.Collections; import org.apache.catalina.Container; import org.apache.catalina.Engine; import org.apache.catalina.Logger; @@ -194,7 +196,7 @@ * The set of currently active Sessions for this Manager, keyed by * session identifier. */ - protected HashMap sessions = new HashMap(); + protected Map sessions = Collections.synchronizedMap(new HashMap()) ; /** @@ -496,11 +498,7 @@ * @param session Session to be added */ public void add(Session session) { - - synchronized (sessions) { - sessions.put(session.getId(), session); - } - + sessions.put(session.getId(), session); } @@ -582,11 +580,8 @@ if (id == null) return (null); - synchronized (sessions) { - Session session = (Session) sessions.get(id); - return (session); - } + return ((Session) sessions.get(id)); } @@ -595,14 +590,7 @@ * If this Manager has no active Sessions, a zero-length array is returned. */ public Session[] findSessions() { - - Session results[] = null; - synchronized (sessions) { - results = new Session[sessions.size()]; - results = (Session[]) sessions.values().toArray(results); - } - return (results); - + return ((Session[]) sessions.values().toArray(new Session[0])); } @@ -612,11 +600,7 @@ * @param session Session to be removed */ public void remove(Session session) { - - synchronized (sessions) { - sessions.remove(session.getId()); - } - + sessions.remove(session.getId()); } Index: StandardManager.java =================================================================== RCS file: /home/cvspublic/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/session/StandardManager.java,v retrieving revision 1.19 diff -u -r1.19 StandardManager.java --- StandardManager.java 9 Jun 2002 02:19:43 -0000 1.19 +++ StandardManager.java 18 Jun 2002 20:47:06 -0000 @@ -80,6 +80,8 @@ import java.io.ObjectStreamClass; import java.util.ArrayList; import java.util.Iterator; +import java.util.HashMap; +import java.util.Collections; import javax.servlet.ServletContext; import org.apache.catalina.Container; import org.apache.catalina.Context; @@ -93,6 +95,7 @@ import org.apache.catalina.Session; import org.apache.catalina.util.CustomObjectInputStream; import org.apache.catalina.util.LifecycleSupport; +import org.apache.commons.collections.LRUMap; /** @@ -138,7 +141,7 @@ /** * The maximum number of active Sessions allowed, or -1 for no limit. */ - private int maxActiveSessions = -1; + private int maxActiveSessions = 10000; /** @@ -274,6 +277,13 @@ new Integer(oldMaxActiveSessions), new Integer(this.maxActiveSessions)); + expireAll(); + + if ( max <= 0 ) + sessions = Collections.synchronizedMap(new HashMap()) ; + else + sessions = Collections.synchronizedMap(new LocalLRUMap(max) ); + } @@ -314,29 +324,6 @@ // --------------------------------------------------------- Public Methods - - /** - * Construct and return a new session object, based on the default - * settings specified by this Manager's properties. The session - * id will be assigned by this method, and available via the getId() - * method of the returned session. If a new session cannot be created - * for any reason, return <code>null</code>. - * - * @exception IllegalStateException if a new session cannot be - * instantiated for any reason - */ - public Session createSession() { - - if ((maxActiveSessions >= 0) && - (sessions.size() >= maxActiveSessions)) - throw new IllegalStateException - (sm.getString("standardManager.createSession.ise")); - - return (super.createSession()); - - } - - /** * Load any currently active sessions that were previously unloaded * to the appropriate persistence mechanism, if any. If persistence is not @@ -353,7 +340,10 @@ // Initialize our internal data structures recycled.clear(); - sessions.clear(); + if ( maxActiveSessions <= 0 ) + sessions = Collections.synchronizedMap(new HashMap() ); + else + sessions = Collections.synchronizedMap(new LocalLRUMap(maxActiveSessions) +); // Open an input stream to the specified pathname, if any File file = file(); @@ -414,7 +404,7 @@ ((StandardSession) session).activate(); } } catch (ClassNotFoundException e) { - log(sm.getString("standardManager.loading.cnfe", e), e); + log(sm.getString("standardManager.loading.cnfe", e), e); if (ois != null) { try { ois.close(); @@ -425,7 +415,7 @@ } throw e; } catch (IOException e) { - log(sm.getString("standardManager.loading.ioe", e), e); + log(sm.getString("standardManager.loading.ioe", e), e); if (ois != null) { try { ois.close(); @@ -492,7 +482,6 @@ } // Write the number of active sessions, followed by the details - ArrayList list = new ArrayList(); synchronized (sessions) { if (debug >= 1) log("Unloading " + sessions.size() + " sessions"); @@ -502,7 +491,6 @@ while (elements.hasNext()) { StandardSession session = (StandardSession) elements.next(); - list.add(session); ((StandardSession) session).passivate(); session.writeObjectData(oos); } @@ -537,18 +525,7 @@ throw e; } - // Expire all the sessions we just wrote - if (debug >= 1) - log("Expiring " + list.size() + " persisted sessions"); - Iterator expires = list.iterator(); - while (expires.hasNext()) { - StandardSession session = (StandardSession) expires.next(); - try { - session.expire(false); - } catch (Throwable t) { - ; - } - } + expireAll(); if (debug >= 1) log("Unloading complete"); @@ -664,18 +641,7 @@ log(sm.getString("standardManager.managerUnload"), e); } - // Expire all active sessions - Session sessions[] = findSessions(); - for (int i = 0; i < sessions.length; i++) { - StandardSession session = (StandardSession) sessions[i]; - if (!session.isValid()) - continue; - try { - session.expire(); - } catch (Throwable t) { - ; - } - } + expireAll(); // Require a new random number generator if we are restarted this.random = null; @@ -714,6 +680,31 @@ // -------------------------------------------------------- Private Methods + /** + * extention of commons LRUMap, handles expiration and works around + * an LRU bug + */ + private class LocalLRUMap extends LRUMap { + LocalLRUMap(int max) { + super(max); + } + + protected void processRemovedLRU(Object key, Object value){ + ((Session)value).expire(); + log("StandardManager: WARNING Max sessions reached, expiring oldest +key:"+key); + } + + // collections 2.0 has a bug in it - fixed in the latest CVS + public Object get(Object key){ + + // put() it to move it to the top of the LRU + if ( containsKey( key ) ){ + put( key, super.get(key)); + } + return super.get(key); + } + }; + /** * Return a File object representing the pathname to our @@ -823,6 +814,27 @@ thread = null; + } + + + /** + * Expire all active sessions + */ + private void expireAll(){ + + Session list[] = findSessions(); + for (int i = 0; i < list.length; i++) { + StandardSession session = (StandardSession) list[i]; + if (!session.isValid()) + continue; + try { + session.expire(); + } catch (Throwable t) { + ; + } + } + + sessions.clear(); } -- To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]> For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>