2 // ========================================================================
3 // Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
19 package org.eclipse.jetty.server.session;
21 import java.io.DataInputStream;
22 import java.io.EOFException;
24 import java.io.FileInputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.util.ArrayList;
28 import java.util.Iterator;
30 import java.util.concurrent.ConcurrentHashMap;
31 import java.util.concurrent.ConcurrentMap;
32 import java.util.concurrent.TimeUnit;
34 import javax.servlet.ServletContext;
35 import javax.servlet.http.HttpServletRequest;
37 import org.eclipse.jetty.server.SessionIdManager;
38 import org.eclipse.jetty.server.handler.ContextHandler;
39 import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
40 import org.eclipse.jetty.util.IO;
41 import org.eclipse.jetty.util.log.Logger;
42 import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
43 import org.eclipse.jetty.util.thread.Scheduler;
46 /* ------------------------------------------------------------ */
50 * An in-memory implementation of SessionManager.
52 * This manager supports saving sessions to disk, either periodically or at shutdown.
53 * Sessions can also have their content idle saved to disk to reduce the memory overheads of large idle sessions.
55 * This manager will create it's own Timer instance to scavenge threads, unless it discovers a shared Timer instance
56 * set as the "org.eclipse.jetty.server.session.timer" attribute of the ContextHandler.
59 public class HashSessionManager extends AbstractSessionManager
61 final static Logger LOG = SessionHandler.LOG;
63 protected final ConcurrentMap<String,HashedSession> _sessions=new ConcurrentHashMap<String,HashedSession>();
64 private Scheduler _timer;
65 private Scheduler.Task _task;
66 long _scavengePeriodMs=30000;
67 long _savePeriodMs=0; //don't do period saves by default
68 long _idleSavePeriodMs = 0; // don't idle save sessions by default.
69 private Scheduler.Task _saveTask;
71 private boolean _lazyLoad=false;
72 private volatile boolean _sessionsLoaded=false;
73 private boolean _deleteUnrestorableSessions=false;
80 protected class Scavenger implements Runnable
91 if (_timer != null && _timer.isRunning())
92 _timer.schedule(this, _scavengePeriodMs, TimeUnit.MILLISECONDS);
101 protected class Saver implements Runnable
116 if (_timer != null && _timer.isRunning())
117 _timer.schedule(this, _savePeriodMs, TimeUnit.MILLISECONDS);
123 /* ------------------------------------------------------------ */
124 public HashSessionManager()
129 /* ------------------------------------------------------------ */
131 * @see AbstractSessionManager#doStart()
134 public void doStart() throws Exception
136 //try shared scheduler from Server first
137 _timer = getSessionHandler().getServer().getBean(Scheduler.class);
140 //try one passed into the context
141 ServletContext context = ContextHandler.getCurrentContext();
143 _timer = (Scheduler)context.getAttribute("org.eclipse.jetty.server.session.timer");
148 //make a scheduler if none useable
149 _timer=new ScheduledExecutorScheduler(toString()+"Timer",true);
150 addBean(_timer,true);
153 addBean(_timer,false);
157 setScavengePeriod(getScavengePeriod());
161 if (!_storeDir.exists())
168 setSavePeriod(getSavePeriod());
171 /* ------------------------------------------------------------ */
173 * @see AbstractSessionManager#doStop()
176 public void doStop() throws Exception
178 // stop the scavengers
190 // This will callback invalidate sessions - where we decide if we will save
197 /* ------------------------------------------------------------ */
199 * @return the period in seconds at which a check is made for sessions to be invalidated.
201 public int getScavengePeriod()
203 return (int)(_scavengePeriodMs/1000);
207 /* ------------------------------------------------------------ */
209 public int getSessions()
211 int sessions=super.getSessions();
212 if (LOG.isDebugEnabled())
214 if (_sessions.size()!=sessions)
215 LOG.warn("sessions: "+_sessions.size()+"!="+sessions);
220 /* ------------------------------------------------------------ */
222 * @return seconds Idle period after which a session is saved
224 public int getIdleSavePeriod()
226 if (_idleSavePeriodMs <= 0)
229 return (int)(_idleSavePeriodMs / 1000);
232 /* ------------------------------------------------------------ */
234 * Configures the period in seconds after which a session is deemed idle and saved
235 * to save on session memory.
237 * The session is persisted, the values attribute map is cleared and the session set to idled.
239 * @param seconds Idle period after which a session is saved
241 public void setIdleSavePeriod(int seconds)
243 _idleSavePeriodMs = seconds * 1000L;
246 /* ------------------------------------------------------------ */
248 public void setMaxInactiveInterval(int seconds)
250 super.setMaxInactiveInterval(seconds);
251 if (_dftMaxIdleSecs>0&&_scavengePeriodMs>_dftMaxIdleSecs*1000L)
252 setScavengePeriod((_dftMaxIdleSecs+9)/10);
255 /* ------------------------------------------------------------ */
257 * @param seconds the period is seconds at which sessions are periodically saved to disk
259 public void setSavePeriod (int seconds)
261 long period = (seconds * 1000L);
264 _savePeriodMs=period;
273 if (_savePeriodMs > 0 && _storeDir!=null) //only save if we have a directory configured
275 _saveTask = _timer.schedule(new Saver(),_savePeriodMs,TimeUnit.MILLISECONDS);
281 /* ------------------------------------------------------------ */
283 * @return the period in seconds at which sessions are periodically saved to disk
285 public int getSavePeriod ()
287 if (_savePeriodMs<=0)
290 return (int)(_savePeriodMs/1000);
293 /* ------------------------------------------------------------ */
295 * @param seconds the period in seconds at which a check is made for sessions to be invalidated.
297 public void setScavengePeriod(int seconds)
302 long old_period=_scavengePeriodMs;
303 long period=seconds*1000L;
309 _scavengePeriodMs=period;
311 if (_timer!=null && (period!=old_period || _task==null))
320 _task = _timer.schedule(new Scavenger(),_scavengePeriodMs, TimeUnit.MILLISECONDS);
325 /* -------------------------------------------------------------- */
327 * Find sessions that have timed out and invalidate them. This runs in the
328 * SessionScavenger thread.
330 protected void scavenge()
332 //don't attempt to scavenge if we are shutting down
333 if (isStopping() || isStopped())
336 Thread thread=Thread.currentThread();
337 ClassLoader old_loader=thread.getContextClassLoader();
341 thread.setContextClassLoader(_loader);
344 long now=System.currentTimeMillis();
345 __log.debug("Scavenging sessions at {}", now);
347 for (Iterator<HashedSession> i=_sessions.values().iterator(); i.hasNext();)
349 HashedSession session=i.next();
350 long idleTime=session.getMaxInactiveInterval()*1000L;
351 if (idleTime>0&&session.getAccessed()+idleTime<now)
353 // Found a stale session, add it to the list
360 __log.warn("Problem scavenging sessions", e);
363 else if (_idleSavePeriodMs > 0 && session.getAccessed()+_idleSavePeriodMs < now)
371 __log.warn("Problem idling session "+ session.getId(), e);
378 thread.setContextClassLoader(old_loader);
382 /* ------------------------------------------------------------ */
384 protected void addSession(AbstractSession session)
387 _sessions.put(session.getClusterId(),(HashedSession)session);
390 /* ------------------------------------------------------------ */
392 public AbstractSession getSession(String idInCluster)
394 if ( _lazyLoad && !_sessionsLoaded)
406 Map<String,HashedSession> sessions=_sessions;
410 HashedSession session = sessions.get(idInCluster);
412 if (session == null && _lazyLoad)
413 session=restoreSession(idInCluster);
417 if (_idleSavePeriodMs!=0)
423 /* ------------------------------------------------------------ */
425 protected void shutdownSessions() throws Exception
427 // Invalidate all sessions to cause unbind events
428 ArrayList<HashedSession> sessions=new ArrayList<HashedSession>(_sessions.values());
430 while (sessions.size()>0 && loop-->0)
432 // If we are called from doStop
433 if (isStopping() && _storeDir != null && _storeDir.exists() && _storeDir.canWrite())
435 // Then we only save and remove the session from memory- it is not invalidated.
436 for (HashedSession session : sessions)
439 _sessions.remove(session.getClusterId());
444 for (HashedSession session : sessions)
445 session.invalidate();
448 // check that no new sessions were created while we were iterating
449 sessions=new ArrayList<HashedSession>(_sessions.values());
455 /* ------------------------------------------------------------ */
457 * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
460 public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
464 Map<String,HashedSession> sessions=_sessions;
465 if (sessions == null)
468 HashedSession session = sessions.remove(oldClusterId);
472 session.remove(); //delete any previously saved session
473 session.setClusterId(newClusterId); //update ids
474 session.setNodeId(newNodeId);
475 session.save(); //save updated session: TODO consider only saving file if idled
476 sessions.put(newClusterId, session);
478 super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId);
486 /* ------------------------------------------------------------ */
488 protected AbstractSession newSession(HttpServletRequest request)
490 return new HashedSession(this, request);
493 /* ------------------------------------------------------------ */
494 protected AbstractSession newSession(long created, long accessed, String clusterId)
496 return new HashedSession(this, created,accessed, clusterId);
499 /* ------------------------------------------------------------ */
501 protected boolean removeSession(String clusterId)
503 return _sessions.remove(clusterId)!=null;
506 /* ------------------------------------------------------------ */
507 public void setStoreDirectory (File dir) throws IOException
509 // CanonicalFile is used to capture the base store directory in a way that will
510 // work on Windows. Case differences may through off later checks using this directory.
511 _storeDir=dir.getCanonicalFile();
514 /* ------------------------------------------------------------ */
515 public File getStoreDirectory ()
520 /* ------------------------------------------------------------ */
521 public void setLazyLoad(boolean lazyLoad)
523 _lazyLoad = lazyLoad;
526 /* ------------------------------------------------------------ */
527 public boolean isLazyLoad()
532 /* ------------------------------------------------------------ */
533 public boolean isDeleteUnrestorableSessions()
535 return _deleteUnrestorableSessions;
538 /* ------------------------------------------------------------ */
539 public void setDeleteUnrestorableSessions(boolean deleteUnrestorableSessions)
541 _deleteUnrestorableSessions = deleteUnrestorableSessions;
544 /* ------------------------------------------------------------ */
545 public void restoreSessions () throws Exception
547 _sessionsLoaded = true;
549 if (_storeDir==null || !_storeDir.exists())
554 if (!_storeDir.canRead())
556 LOG.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath());
560 String[] files = _storeDir.list();
561 for (int i=0;files!=null&&i<files.length;i++)
563 restoreSession(files[i]);
567 /* ------------------------------------------------------------ */
568 protected synchronized HashedSession restoreSession(String idInCuster)
570 File file = new File(_storeDir,idInCuster);
572 FileInputStream in = null;
573 Exception error = null;
578 in = new FileInputStream(file);
579 HashedSession session = restoreSession(in, null);
580 addSession(session, false);
581 session.didActivate();
591 if (in != null) IO.close(in);
595 if (isDeleteUnrestorableSessions() && file.exists() && file.getParentFile().equals(_storeDir) )
598 LOG.warn("Deleting file for unrestorable session "+idInCuster, error);
602 __log.warn("Problem restoring session "+idInCuster, error);
606 file.delete(); //delete successfully restored file
611 /* ------------------------------------------------------------ */
612 public void saveSessions(boolean reactivate) throws Exception
614 if (_storeDir==null || !_storeDir.exists())
619 if (!_storeDir.canWrite())
621 LOG.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable");
625 for (HashedSession session : _sessions.values())
626 session.save(reactivate);
630 /* ------------------------------------------------------------ */
631 public HashedSession restoreSession (InputStream is, HashedSession session) throws Exception
633 DataInputStream di = new DataInputStream(is);
635 String clusterId = di.readUTF();
636 di.readUTF(); // nodeId
638 long created = di.readLong();
639 long accessed = di.readLong();
640 int requests = di.readInt();
643 session = (HashedSession)newSession(created, accessed, clusterId);
644 session.setRequests(requests);
646 int size = di.readInt();
648 restoreSessionAttributes(di, size, session);
652 int maxIdle = di.readInt();
653 session.setMaxInactiveInterval(maxIdle);
655 catch (EOFException e)
657 LOG.debug("No maxInactiveInterval persisted for session "+clusterId);
665 private void restoreSessionAttributes (InputStream is, int size, HashedSession session)
670 ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(is);
671 for (int i=0; i<size;i++)
673 String key = ois.readUTF();
674 Object value = ois.readObject();
675 session.setAttribute(key,value);