]> WPIA git - gigi.git/blob - lib/jetty/org/eclipse/jetty/server/session/HashSessionManager.java
Importing upstream Jetty jetty-9.2.1.v20140609
[gigi.git] / lib / jetty / org / eclipse / jetty / server / session / HashSessionManager.java
1 //
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.
8 //
9 //      The Eclipse Public License is available at
10 //      http://www.eclipse.org/legal/epl-v10.html
11 //
12 //      The Apache License v2.0 is available at
13 //      http://www.opensource.org/licenses/apache2.0.php
14 //
15 //  You may elect to redistribute this code under either of these licenses.
16 //  ========================================================================
17 //
18
19 package org.eclipse.jetty.server.session;
20
21 import java.io.DataInputStream;
22 import java.io.EOFException;
23 import java.io.File;
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;
29 import java.util.Map;
30 import java.util.concurrent.ConcurrentHashMap;
31 import java.util.concurrent.ConcurrentMap;
32 import java.util.concurrent.TimeUnit;
33
34 import javax.servlet.ServletContext;
35 import javax.servlet.http.HttpServletRequest;
36
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;
44
45
46 /* ------------------------------------------------------------ */
47 /** 
48  * HashSessionManager
49  * 
50  * An in-memory implementation of SessionManager.
51  * <p>
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.
54  * <p>
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.
57  *
58  */
59 public class HashSessionManager extends AbstractSessionManager
60 {
61     final static Logger LOG = SessionHandler.LOG;
62
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;
70     File _storeDir;
71     private boolean _lazyLoad=false;
72     private volatile boolean _sessionsLoaded=false;
73     private boolean _deleteUnrestorableSessions=false;
74
75
76     /**
77      * Scavenger
78      *
79      */
80     protected class Scavenger implements Runnable
81     {
82         @Override
83         public void run()
84         {
85             try
86             {
87                 scavenge();
88             }
89             finally
90             {
91                 if (_timer != null && _timer.isRunning())
92                     _timer.schedule(this, _scavengePeriodMs, TimeUnit.MILLISECONDS);
93             }
94         }
95     }
96
97     /**
98      * Saver
99      *
100      */
101     protected class Saver implements Runnable
102     {
103         @Override
104         public void run()
105         {
106             try
107             {
108                 saveSessions(true);
109             }
110             catch (Exception e)
111             {       
112                 LOG.warn(e);
113             }
114             finally
115             {
116                 if (_timer != null && _timer.isRunning())
117                     _timer.schedule(this, _savePeriodMs, TimeUnit.MILLISECONDS);
118             }
119         }        
120     }
121
122
123     /* ------------------------------------------------------------ */
124     public HashSessionManager()
125     {
126         super();
127     }
128
129     /* ------------------------------------------------------------ */
130     /**
131      * @see AbstractSessionManager#doStart()
132      */
133     @Override
134     public void doStart() throws Exception
135     {
136         //try shared scheduler from Server first
137         _timer = getSessionHandler().getServer().getBean(Scheduler.class);
138         if (_timer == null)
139         {
140             //try one passed into the context
141             ServletContext context = ContextHandler.getCurrentContext();
142             if (context!=null)
143                 _timer = (Scheduler)context.getAttribute("org.eclipse.jetty.server.session.timer");   
144         }         
145       
146         if (_timer == null)
147         {
148             //make a scheduler if none useable
149             _timer=new ScheduledExecutorScheduler(toString()+"Timer",true);
150             addBean(_timer,true);
151         }
152         else
153             addBean(_timer,false);
154             
155         super.doStart();
156
157         setScavengePeriod(getScavengePeriod());
158
159         if (_storeDir!=null)
160         {
161             if (!_storeDir.exists())
162                 _storeDir.mkdirs();
163
164             if (!_lazyLoad)
165                 restoreSessions();
166         }
167
168         setSavePeriod(getSavePeriod());
169     }
170
171     /* ------------------------------------------------------------ */
172     /**
173      * @see AbstractSessionManager#doStop()
174      */
175     @Override
176     public void doStop() throws Exception
177     {
178         // stop the scavengers
179         synchronized(this)
180         {
181             if (_saveTask!=null)
182                 _saveTask.cancel();
183             _saveTask=null;
184             if (_task!=null)
185                 _task.cancel();
186             _task=null;
187             _timer=null;
188         }
189
190         // This will callback invalidate sessions - where we decide if we will save
191         super.doStop();
192
193         _sessions.clear();
194
195     }
196
197     /* ------------------------------------------------------------ */
198     /**
199      * @return the period in seconds at which a check is made for sessions to be invalidated.
200      */
201     public int getScavengePeriod()
202     {
203         return (int)(_scavengePeriodMs/1000);
204     }
205
206
207     /* ------------------------------------------------------------ */
208     @Override
209     public int getSessions()
210     {
211         int sessions=super.getSessions();
212         if (LOG.isDebugEnabled())
213         {
214             if (_sessions.size()!=sessions)
215                 LOG.warn("sessions: "+_sessions.size()+"!="+sessions);
216         }
217         return sessions;
218     }
219
220     /* ------------------------------------------------------------ */
221     /**
222      * @return seconds Idle period after which a session is saved
223      */
224     public int getIdleSavePeriod()
225     {
226       if (_idleSavePeriodMs <= 0)
227         return 0;
228
229       return (int)(_idleSavePeriodMs / 1000);
230     }
231
232     /* ------------------------------------------------------------ */
233     /**
234      * Configures the period in seconds after which a session is deemed idle and saved
235      * to save on session memory.
236      *
237      * The session is persisted, the values attribute map is cleared and the session set to idled.
238      *
239      * @param seconds Idle period after which a session is saved
240      */
241     public void setIdleSavePeriod(int seconds)
242     {
243       _idleSavePeriodMs = seconds * 1000L;
244     }
245
246     /* ------------------------------------------------------------ */
247     @Override
248     public void setMaxInactiveInterval(int seconds)
249     {
250         super.setMaxInactiveInterval(seconds);
251         if (_dftMaxIdleSecs>0&&_scavengePeriodMs>_dftMaxIdleSecs*1000L)
252             setScavengePeriod((_dftMaxIdleSecs+9)/10);
253     }
254
255     /* ------------------------------------------------------------ */
256     /**
257      * @param seconds the period is seconds at which sessions are periodically saved to disk
258      */
259     public void setSavePeriod (int seconds)
260     {
261         long period = (seconds * 1000L);
262         if (period < 0)
263             period=0;
264         _savePeriodMs=period;
265
266         if (_timer!=null)
267         {
268             synchronized (this)
269             {
270                 if (_saveTask!=null)
271                     _saveTask.cancel();
272                 _saveTask = null;
273                 if (_savePeriodMs > 0 && _storeDir!=null) //only save if we have a directory configured
274                 {
275                     _saveTask = _timer.schedule(new Saver(),_savePeriodMs,TimeUnit.MILLISECONDS);
276                 }
277             }
278         }
279     }
280
281     /* ------------------------------------------------------------ */
282     /**
283      * @return the period in seconds at which sessions are periodically saved to disk
284      */
285     public int getSavePeriod ()
286     {
287         if (_savePeriodMs<=0)
288             return 0;
289
290         return (int)(_savePeriodMs/1000);
291     }
292
293     /* ------------------------------------------------------------ */
294     /**
295      * @param seconds the period in seconds at which a check is made for sessions to be invalidated.
296      */
297     public void setScavengePeriod(int seconds)
298     { 
299         if (seconds==0)
300             seconds=60;
301
302         long old_period=_scavengePeriodMs;
303         long period=seconds*1000L;
304         if (period>60000)
305             period=60000;
306         if (period<1000)
307             period=1000;
308
309         _scavengePeriodMs=period;
310     
311         if (_timer!=null && (period!=old_period || _task==null))
312         {
313             synchronized (this)
314             {
315                 if (_task!=null)
316                 {
317                     _task.cancel();
318                     _task = null;
319                 }
320                 _task = _timer.schedule(new Scavenger(),_scavengePeriodMs, TimeUnit.MILLISECONDS);
321             }
322         }
323     }
324
325     /* -------------------------------------------------------------- */
326     /**
327      * Find sessions that have timed out and invalidate them. This runs in the
328      * SessionScavenger thread.
329      */
330     protected void scavenge()
331     {
332         //don't attempt to scavenge if we are shutting down
333         if (isStopping() || isStopped())
334             return;
335
336         Thread thread=Thread.currentThread();
337         ClassLoader old_loader=thread.getContextClassLoader();
338         try
339         {      
340             if (_loader!=null)
341                 thread.setContextClassLoader(_loader);
342
343             // For each session
344             long now=System.currentTimeMillis();
345             __log.debug("Scavenging sessions at {}", now); 
346             
347             for (Iterator<HashedSession> i=_sessions.values().iterator(); i.hasNext();)
348             {
349                 HashedSession session=i.next();
350                 long idleTime=session.getMaxInactiveInterval()*1000L; 
351                 if (idleTime>0&&session.getAccessed()+idleTime<now)
352                 {
353                     // Found a stale session, add it to the list
354                     try
355                     {
356                         session.timeout();
357                     }
358                     catch (Exception e)
359                     {
360                         __log.warn("Problem scavenging sessions", e);
361                     }
362                 }
363                 else if (_idleSavePeriodMs > 0 && session.getAccessed()+_idleSavePeriodMs < now)
364                 {
365                     try
366                     {
367                         session.idle();
368                     }
369                     catch (Exception e)
370                     {
371                         __log.warn("Problem idling session "+ session.getId(), e);
372                     }
373                 }
374             }
375         }       
376         finally
377         {
378             thread.setContextClassLoader(old_loader);
379         }
380     }
381
382     /* ------------------------------------------------------------ */
383     @Override
384     protected void addSession(AbstractSession session)
385     {
386         if (isRunning())
387             _sessions.put(session.getClusterId(),(HashedSession)session);
388     }
389
390     /* ------------------------------------------------------------ */
391     @Override
392     public AbstractSession getSession(String idInCluster)
393     {
394         if ( _lazyLoad && !_sessionsLoaded)
395         {
396             try
397             {
398                 restoreSessions();
399             }
400             catch(Exception e)
401             {
402                 LOG.warn(e);
403             }
404         }
405
406         Map<String,HashedSession> sessions=_sessions;
407         if (sessions==null)
408             return null;
409
410         HashedSession session = sessions.get(idInCluster);
411
412         if (session == null && _lazyLoad)
413             session=restoreSession(idInCluster);
414         if (session == null)
415             return null;
416
417         if (_idleSavePeriodMs!=0)
418             session.deIdle();
419
420         return session;
421     }
422
423     /* ------------------------------------------------------------ */
424     @Override
425     protected void shutdownSessions() throws Exception
426     {   
427         // Invalidate all sessions to cause unbind events
428         ArrayList<HashedSession> sessions=new ArrayList<HashedSession>(_sessions.values());
429         int loop=100;
430         while (sessions.size()>0 && loop-->0)
431         {
432             // If we are called from doStop
433             if (isStopping() && _storeDir != null && _storeDir.exists() && _storeDir.canWrite())
434             {
435                 // Then we only save and remove the session from memory- it is not invalidated.
436                 for (HashedSession session : sessions)
437                 {
438                     session.save(false);
439                     _sessions.remove(session.getClusterId());
440                 }
441             }
442             else
443             {
444                 for (HashedSession session : sessions)
445                     session.invalidate();
446             }
447
448             // check that no new sessions were created while we were iterating
449             sessions=new ArrayList<HashedSession>(_sessions.values());
450         }
451     }
452     
453     
454     
455     /* ------------------------------------------------------------ */
456     /**
457      * @see org.eclipse.jetty.server.SessionManager#renewSessionId(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
458      */
459     @Override
460     public void renewSessionId(String oldClusterId, String oldNodeId, String newClusterId, String newNodeId)
461     {
462         try
463         {
464             Map<String,HashedSession> sessions=_sessions;
465             if (sessions == null)
466                 return;
467
468             HashedSession session = sessions.remove(oldClusterId);
469             if (session == null)
470                 return;
471
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);
477             
478             super.renewSessionId(oldClusterId, oldNodeId, newClusterId, newNodeId);
479         }
480         catch (Exception e)
481         {
482             LOG.warn(e);
483         }
484     }
485
486     /* ------------------------------------------------------------ */
487     @Override
488     protected AbstractSession newSession(HttpServletRequest request)
489     {
490         return new HashedSession(this, request);
491     }
492
493     /* ------------------------------------------------------------ */
494     protected AbstractSession newSession(long created, long accessed, String clusterId)
495     {
496         return new HashedSession(this, created,accessed, clusterId);
497     }
498
499     /* ------------------------------------------------------------ */
500     @Override
501     protected boolean removeSession(String clusterId)
502     {
503         return _sessions.remove(clusterId)!=null;
504     }
505
506     /* ------------------------------------------------------------ */
507     public void setStoreDirectory (File dir) throws IOException
508     { 
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();
512     }
513
514     /* ------------------------------------------------------------ */
515     public File getStoreDirectory ()
516     {
517         return _storeDir;
518     }
519
520     /* ------------------------------------------------------------ */
521     public void setLazyLoad(boolean lazyLoad)
522     {
523         _lazyLoad = lazyLoad;
524     }
525
526     /* ------------------------------------------------------------ */
527     public boolean isLazyLoad()
528     {
529         return _lazyLoad;
530     }
531
532     /* ------------------------------------------------------------ */
533     public boolean isDeleteUnrestorableSessions()
534     {
535         return _deleteUnrestorableSessions;
536     }
537
538     /* ------------------------------------------------------------ */
539     public void setDeleteUnrestorableSessions(boolean deleteUnrestorableSessions)
540     {
541         _deleteUnrestorableSessions = deleteUnrestorableSessions;
542     }
543
544     /* ------------------------------------------------------------ */
545     public void restoreSessions () throws Exception
546     {
547         _sessionsLoaded = true;
548
549         if (_storeDir==null || !_storeDir.exists())
550         {
551             return;
552         }
553
554         if (!_storeDir.canRead())
555         {
556             LOG.warn ("Unable to restore Sessions: Cannot read from Session storage directory "+_storeDir.getAbsolutePath());
557             return;
558         }
559
560         String[] files = _storeDir.list();
561         for (int i=0;files!=null&&i<files.length;i++)
562         {
563             restoreSession(files[i]);
564         }
565     }
566
567     /* ------------------------------------------------------------ */
568     protected synchronized HashedSession restoreSession(String idInCuster)
569     {        
570         File file = new File(_storeDir,idInCuster);
571
572         FileInputStream in = null;
573         Exception error = null;
574         try
575         {
576             if (file.exists())
577             {
578                 in = new FileInputStream(file);
579                 HashedSession session = restoreSession(in, null);
580                 addSession(session, false);
581                 session.didActivate();
582                 return session;
583             }
584         }
585         catch (Exception e)
586         {
587            error = e;
588         }
589         finally
590         {
591             if (in != null) IO.close(in);
592             
593             if (error != null)
594             {
595                 if (isDeleteUnrestorableSessions() && file.exists() && file.getParentFile().equals(_storeDir) )
596                 {
597                     file.delete();
598                     LOG.warn("Deleting file for unrestorable session "+idInCuster, error);
599                 }
600                 else
601                 {
602                     __log.warn("Problem restoring session "+idInCuster, error);
603                 }
604             }
605             else
606                file.delete(); //delete successfully restored file
607         }
608         return null;
609     }
610
611     /* ------------------------------------------------------------ */
612     public void saveSessions(boolean reactivate) throws Exception
613     {
614         if (_storeDir==null || !_storeDir.exists())
615         {
616             return;
617         }
618
619         if (!_storeDir.canWrite())
620         {
621             LOG.warn ("Unable to save Sessions: Session persistence storage directory "+_storeDir.getAbsolutePath()+ " is not writeable");
622             return;
623         }
624
625         for (HashedSession session : _sessions.values())
626             session.save(reactivate);
627     }
628     
629
630     /* ------------------------------------------------------------ */
631     public HashedSession restoreSession (InputStream is, HashedSession session) throws Exception
632     {
633         DataInputStream di = new DataInputStream(is);
634
635         String clusterId = di.readUTF();
636         di.readUTF(); // nodeId
637
638         long created = di.readLong();
639         long accessed = di.readLong();
640         int requests = di.readInt();
641
642         if (session == null)
643             session = (HashedSession)newSession(created, accessed, clusterId);
644         session.setRequests(requests);
645
646         int size = di.readInt();
647
648         restoreSessionAttributes(di, size, session);
649
650         try
651         {
652             int maxIdle = di.readInt();
653             session.setMaxInactiveInterval(maxIdle);
654         }
655         catch (EOFException e)
656         {
657             LOG.debug("No maxInactiveInterval persisted for session "+clusterId);
658             LOG.ignore(e);
659         }
660
661         return session;
662     }
663
664     
665     private void restoreSessionAttributes (InputStream is, int size, HashedSession session)
666     throws Exception
667     {
668         if (size>0)
669         {
670             ClassLoadingObjectInputStream ois =  new ClassLoadingObjectInputStream(is);
671             for (int i=0; i<size;i++)
672             {
673                 String key = ois.readUTF();
674                 Object value = ois.readObject();
675                 session.setAttribute(key,value);
676             }
677         }
678     }
679 }