2 // ========================================================================
3 // Copyright (c) 1995-2016 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;
23 import java.io.FileInputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.util.ArrayList;
27 import java.util.Iterator;
29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.concurrent.ConcurrentMap;
31 import java.util.concurrent.TimeUnit;
33 import javax.servlet.ServletContext;
34 import javax.servlet.http.HttpServletRequest;
36 import org.eclipse.jetty.server.handler.ContextHandler;
37 import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
38 import org.eclipse.jetty.util.log.Logger;
39 import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
40 import org.eclipse.jetty.util.thread.Scheduler;
43 /* ------------------------------------------------------------ */
47 * An in-memory implementation of SessionManager.
49 * This manager supports saving sessions to disk, either periodically or at shutdown.
50 * Sessions can also have their content idle saved to disk to reduce the memory overheads of large idle sessions.
52 * This manager will create it's own Timer instance to scavenge threads, unless it discovers a shared Timer instance
53 * set as the "org.eclipse.jetty.server.session.timer" attribute of the ContextHandler.
56 public class HashSessionManager extends AbstractSessionManager
58 final static Logger LOG = SessionHandler.LOG;
60 protected final ConcurrentMap<String,HashedSession> _sessions=new ConcurrentHashMap<String,HashedSession>();
61 private Scheduler _timer;
62 private Scheduler.Task _task;
63 long _scavengePeriodMs=30000;
64 long _savePeriodMs=0; //don't do period saves by default
65 long _idleSavePeriodMs = 0; // don't idle save sessions by default.
66 private Scheduler.Task _saveTask;
68 private boolean _lazyLoad=false;
69 private volatile boolean _sessionsLoaded=false;
70 private boolean _deleteUnrestorableSessions=false;
77 protected class Scavenger implements Runnable
88 if (_timer != null && _timer.isRunning()) {
89 _task = _timer.schedule(this, _scavengePeriodMs, TimeUnit.MILLISECONDS);
99 protected class Saver implements Runnable
114 if (_timer != null && _timer.isRunning())
115 _saveTask = _timer.schedule(this, _savePeriodMs, TimeUnit.MILLISECONDS);
121 /* ------------------------------------------------------------ */
122 public HashSessionManager()
127 /* ------------------------------------------------------------ */
129 * @see AbstractSessionManager#doStart()
132 public void doStart() throws Exception
134 //try shared scheduler from Server first
135 _timer = getSessionHandler().getServer().getBean(Scheduler.class);
138 //try one passed into the context
139 ServletContext context = ContextHandler.getCurrentContext();
141 _timer = (Scheduler)context.getAttribute("org.eclipse.jetty.server.session.timer");
146 //make a scheduler if none useable
147 _timer=new ScheduledExecutorScheduler(toString()+"Timer",true);
148 addBean(_timer,true);
151 addBean(_timer,false);
155 setScavengePeriod(getScavengePeriod());
159 if (!_storeDir.exists())
166 setSavePeriod(getSavePeriod());
169 /* ------------------------------------------------------------ */
171 * @see AbstractSessionManager#doStop()
174 public void doStop() throws Exception
176 // stop the scavengers
191 // This will callback invalidate sessions - where we decide if we will save
198 /* ------------------------------------------------------------ */
200 * @return the period in seconds at which a check is made for sessions to be invalidated.
202 public int getScavengePeriod()
204 return (int)(_scavengePeriodMs/1000);
208 /* ------------------------------------------------------------ */
210 public int getSessions()
212 int sessions=super.getSessions();
213 if (LOG.isDebugEnabled())
215 if (_sessions.size()!=sessions)
216 LOG.warn("sessions: "+_sessions.size()+"!="+sessions);
221 /* ------------------------------------------------------------ */
223 * @return seconds Idle period after which a session is saved
225 public int getIdleSavePeriod()
227 if (_idleSavePeriodMs <= 0)
230 return (int)(_idleSavePeriodMs / 1000);
233 /* ------------------------------------------------------------ */
235 * Configures the period in seconds after which a session is deemed idle and saved
236 * to save on session memory.
238 * The session is persisted, the values attribute map is cleared and the session set to idled.
240 * @param seconds Idle period after which a session is saved
242 public void setIdleSavePeriod(int seconds)
244 _idleSavePeriodMs = seconds * 1000L;
247 /* ------------------------------------------------------------ */
249 public void setMaxInactiveInterval(int seconds)
251 super.setMaxInactiveInterval(seconds);
252 if (_dftMaxIdleSecs>0&&_scavengePeriodMs>_dftMaxIdleSecs*1000L)
253 setScavengePeriod((_dftMaxIdleSecs+9)/10);
256 /* ------------------------------------------------------------ */
258 * @param seconds the period is seconds at which sessions are periodically saved to disk
260 public void setSavePeriod (int seconds)
262 long period = (seconds * 1000L);
265 _savePeriodMs=period;
274 if (_savePeriodMs > 0 && _storeDir!=null) //only save if we have a directory configured
276 _saveTask = _timer.schedule(new Saver(),_savePeriodMs,TimeUnit.MILLISECONDS);
282 /* ------------------------------------------------------------ */
284 * @return the period in seconds at which sessions are periodically saved to disk
286 public int getSavePeriod ()
288 if (_savePeriodMs<=0)
291 return (int)(_savePeriodMs/1000);
294 /* ------------------------------------------------------------ */
296 * @param seconds the period in seconds at which a check is made for sessions to be invalidated.
298 public void setScavengePeriod(int seconds)
303 long old_period=_scavengePeriodMs;
304 long period=seconds*1000L;
310 _scavengePeriodMs=period;
314 if (_timer!=null && (period!=old_period || _task==null))
322 _task = _timer.schedule(new Scavenger(),_scavengePeriodMs, TimeUnit.MILLISECONDS);
327 /* -------------------------------------------------------------- */
329 * Find sessions that have timed out and invalidate them. This runs in the
330 * SessionScavenger thread.
332 protected void scavenge()
334 //don't attempt to scavenge if we are shutting down
335 if (isStopping() || isStopped())
338 Thread thread=Thread.currentThread();
339 ClassLoader old_loader=thread.getContextClassLoader();
343 thread.setContextClassLoader(_loader);
346 long now=System.currentTimeMillis();
347 __log.debug("Scavenging sessions at {}", now);
349 for (Iterator<HashedSession> i=_sessions.values().iterator(); i.hasNext();)
351 HashedSession session=i.next();
352 long idleTime=session.getMaxInactiveInterval()*1000L;
353 if (idleTime>0&&session.getAccessed()+idleTime<now)
355 // Found a stale session, add it to the list
362 __log.warn("Problem scavenging sessions", e);
365 else if (_idleSavePeriodMs > 0 && session.getAccessed()+_idleSavePeriodMs < now)
373 __log.warn("Problem idling session "+ session.getId(), e);
380 thread.setContextClassLoader(old_loader);
384 /* ------------------------------------------------------------ */
386 protected void addSession(AbstractSession session)
389 _sessions.put(session.getClusterId(),(HashedSession)session);
392 /* ------------------------------------------------------------ */
394 public AbstractSession getSession(String idInCluster)
396 if ( _lazyLoad && !_sessionsLoaded)
408 Map<String,HashedSession> sessions=_sessions;
412 HashedSession session = sessions.get(idInCluster);
414 if (session == null && _lazyLoad)
415 session=restoreSession(idInCluster);
419 if (_idleSavePeriodMs!=0)
425 /* ------------------------------------------------------------ */
427 protected void shutdownSessions() throws Exception
429 // Invalidate all sessions to cause unbind events
430 ArrayList<HashedSession> sessions=new ArrayList<HashedSession>(_sessions.values());
432 while (sessions.size()>0 && loop-->0)
434 // If we are called from doStop
435 if (isStopping() && _storeDir != null && _storeDir.exists() && _storeDir.canWrite())
437 // Then we only save and remove the session from memory- it is not invalidated.
438 for (HashedSession session : sessions)
441 _sessions.remove(session.getClusterId());
446 for (HashedSession session : sessions)
447 session.invalidate();
450 // check that no new sessions were created while we were iterating
451 sessions=new ArrayList<HashedSession>(_sessions.values());
457 /* ------------------------------------------------------------ */
459 * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
462 public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
466 Map<String,HashedSession> sessions=_sessions;
467 if (sessions == null)
470 HashedSession session = sessions.remove(oldClusterId);
474 session.remove(); //delete any previously saved session
475 session.setClusterId(newClusterId); //update ids
476 session.setNodeId(newNodeId);
477 session.save(); //save updated session: TODO consider only saving file if idled
478 sessions.put(newClusterId, session);
480 super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId);
488 /* ------------------------------------------------------------ */
490 protected AbstractSession newSession(HttpServletRequest request)
492 return new HashedSession(this, request);
495 /* ------------------------------------------------------------ */
496 protected AbstractSession newSession(long created, long accessed, String clusterId)
498 return new HashedSession(this, created,accessed, clusterId);
501 /* ------------------------------------------------------------ */
503 protected boolean removeSession(String clusterId)
505 return _sessions.remove(clusterId)!=null;
508 /* ------------------------------------------------------------ */
509 public void setStoreDirectory (File dir) throws IOException
511 // CanonicalFile is used to capture the base store directory in a way that will
512 // work on Windows. Case differences may through off later checks using this directory.
513 _storeDir=dir.getCanonicalFile();
516 /* ------------------------------------------------------------ */
517 public File getStoreDirectory ()
522 /* ------------------------------------------------------------ */
523 public void setLazyLoad(boolean lazyLoad)
525 _lazyLoad = lazyLoad;
528 /* ------------------------------------------------------------ */
529 public boolean isLazyLoad()
534 /* ------------------------------------------------------------ */
535 public boolean isDeleteUnrestorableSessions()
537 return _deleteUnrestorableSessions;
540 /* ------------------------------------------------------------ */
541 public void setDeleteUnrestorableSessions(boolean deleteUnrestorableSessions)
543 _deleteUnrestorableSessions = deleteUnrestorableSessions;
546 /* ------------------------------------------------------------ */
547 public void restoreSessions () throws Exception
549 _sessionsLoaded = true;
551 if (_storeDir==null || !_storeDir.exists())
556 if (!_storeDir.canRead())
558 LOG.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath());
562 String[] files = _storeDir.list();
563 for (int i=0;files!=null&&i<files.length;i++)
565 restoreSession(files[i]);
569 /* ------------------------------------------------------------ */
570 protected synchronized HashedSession restoreSession(String idInCuster)
572 File file = new File(_storeDir,idInCuster);
574 Exception error = null;
577 if (LOG.isDebugEnabled())
579 LOG.debug("Not loading: {}",file);
584 try (FileInputStream in = new FileInputStream(file))
586 HashedSession session = restoreSession(in,null);
587 addSession(session,false);
588 session.didActivate();
599 if (isDeleteUnrestorableSessions() && file.exists() && file.getParentFile().equals(_storeDir) )
602 LOG.warn("Deleting file for unrestorable session "+idInCuster, error);
606 __log.warn("Problem restoring session "+idInCuster, error);
611 // delete successfully restored file
618 /* ------------------------------------------------------------ */
619 public void saveSessions(boolean reactivate) throws Exception
621 if (_storeDir==null || !_storeDir.exists())
626 if (!_storeDir.canWrite())
628 LOG.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable");
632 for (HashedSession session : _sessions.values())
633 session.save(reactivate);
637 /* ------------------------------------------------------------ */
638 public HashedSession restoreSession (InputStream is, HashedSession session) throws Exception
640 DataInputStream di = new DataInputStream(is);
642 String clusterId = di.readUTF();
643 di.readUTF(); // nodeId
645 long created = di.readLong();
646 long accessed = di.readLong();
647 int requests = di.readInt();
650 session = (HashedSession)newSession(created, accessed, clusterId);
652 session.setRequests(requests);
655 int size = di.readInt();
657 restoreSessionAttributes(di, size, session);
661 int maxIdle = di.readInt();
662 session.setMaxInactiveInterval(maxIdle);
664 catch (IOException e)
666 LOG.debug("No maxInactiveInterval persisted for session "+clusterId);
674 @SuppressWarnings("resource")
675 private void restoreSessionAttributes (InputStream is, int size, HashedSession session)
680 // input stream should not be closed here
681 ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(is);
682 for (int i=0; i<size;i++)
684 String key = ois.readUTF();
685 Object value = ois.readObject();
686 session.setAttribute(key,value);