1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.mortbay.terracotta.servlet;
16
17 import java.util.Collections;
18 import java.util.Enumeration;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.Hashtable;
22 import java.util.Map;
23 import java.util.Set;
24 import java.util.concurrent.Executors;
25 import java.util.concurrent.ScheduledExecutorService;
26 import java.util.concurrent.ScheduledFuture;
27 import java.util.concurrent.TimeUnit;
28
29 import javax.servlet.http.Cookie;
30 import javax.servlet.http.HttpServletRequest;
31 import javax.servlet.http.HttpSession;
32
33 import com.tc.object.bytecode.Manageable;
34 import com.tc.object.bytecode.Manager;
35 import com.tc.object.bytecode.ManagerUtil;
36 import org.mortbay.jetty.Request;
37 import org.mortbay.jetty.handler.ContextHandler;
38 import org.mortbay.jetty.servlet.AbstractSessionManager;
39 import org.mortbay.log.Log;
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85 public class TerracottaSessionManager extends AbstractSessionManager implements Runnable
86 {
87
88
89
90 private Map<String, Session> _sessions;
91
92
93
94
95
96
97 private Hashtable<String, SessionData> _sessionDatas;
98
99
100
101
102
103 private Hashtable<String, MutableLong> _sessionExpirations;
104 private String _contextPath;
105 private String _virtualHost;
106 private long _scavengePeriodMs = 30000;
107 private ScheduledExecutorService _scheduler;
108 private ScheduledFuture<?> _scavenger;
109
110 public void doStart() throws Exception
111 {
112 super.doStart();
113
114 _contextPath = canonicalize(_context.getContextPath());
115 _virtualHost = virtualHostFrom(_context);
116
117 _sessions = Collections.synchronizedMap(new HashMap<String, Session>());
118 _sessionDatas = newSharedMap("sessionData:" + _contextPath + ":" + _virtualHost);
119 _sessionExpirations = newSharedMap("sessionExpirations:" + _contextPath + ":" + _virtualHost);
120 _scheduler = Executors.newSingleThreadScheduledExecutor();
121 scheduleScavenging();
122 }
123
124 private Hashtable newSharedMap(String name)
125 {
126
127
128 Lock.lock(name);
129 try
130 {
131
132
133
134 Hashtable result = (Hashtable)ManagerUtil.lookupOrCreateRootNoDepth(name, new Hashtable());
135 ((Manageable)result).__tc_managed().disableAutoLocking();
136 return result;
137 }
138 finally
139 {
140 Lock.unlock(name);
141 }
142 }
143
144 private void scheduleScavenging()
145 {
146 if (_scavenger != null)
147 {
148 _scavenger.cancel(false);
149 _scavenger = null;
150 }
151 long scavengePeriod = getScavengePeriodMs();
152 if (scavengePeriod > 0 && _scheduler != null)
153 _scavenger = _scheduler.scheduleWithFixedDelay(this, scavengePeriod, scavengePeriod, TimeUnit.MILLISECONDS);
154 }
155
156 public void doStop() throws Exception
157 {
158 if (_scavenger != null) _scavenger.cancel(true);
159 if (_scheduler != null) _scheduler.shutdownNow();
160 super.doStop();
161 }
162
163 public void run()
164 {
165 scavenge();
166 }
167
168 public void enter(Request request)
169 {
170
171
172
173
174
175
176 String requestedSessionId = request.getRequestedSessionId();
177 HttpSession session = request.getSession(false);
178 Log.debug("Entering, requested session id {}, session id {}", requestedSessionId, session == null ? null : getClusterId(session));
179 if (requestedSessionId == null)
180 {
181
182
183
184 }
185 else
186 {
187
188
189
190
191
192 enter(getIdManager().getClusterId(requestedSessionId));
193 }
194 }
195
196 protected void enter(String clusterId)
197 {
198 Lock.lock(newLockId(clusterId));
199 Log.debug("Entered, session id {}", clusterId);
200 }
201
202 protected boolean tryEnter(String clusterId)
203 {
204 return Lock.tryLock(newLockId(clusterId));
205 }
206
207 public void exit(Request request)
208 {
209
210
211
212
213
214
215 String requestedSessionId = request.getRequestedSessionId();
216 HttpSession session = request.getSession(false);
217 Log.debug("Exiting, requested session id {}, session id {}", requestedSessionId, session == null ? null : getClusterId(session));
218 if (requestedSessionId == null)
219 {
220 if (session == null)
221 {
222
223 }
224 else
225 {
226
227 exit(getClusterId(session));
228 }
229 }
230 else
231 {
232
233 String requestedClusterId = getIdManager().getClusterId(requestedSessionId);
234 exit(requestedClusterId);
235
236 if (session != null)
237 {
238 if (!requestedClusterId.equals(getClusterId(session)))
239 {
240
241
242
243 exit(getClusterId(session));
244 }
245 }
246 }
247 }
248
249 protected void exit(String clusterId)
250 {
251 Lock.unlock(newLockId(clusterId));
252 Log.debug("Exited, session id {}", clusterId);
253 }
254
255 protected void addSession(AbstractSessionManager.Session session)
256 {
257
258
259
260
261
262 String clusterId = getClusterId(session);
263 Session tcSession = (Session)session;
264 SessionData sessionData = tcSession.getSessionData();
265 _sessionExpirations.put(clusterId, sessionData._expiration);
266 _sessionDatas.put(clusterId, sessionData);
267 _sessions.put(clusterId, tcSession);
268 Log.debug("Added session {} with id {}", tcSession, clusterId);
269 }
270
271 @Override
272 public Cookie access(HttpSession session, boolean secure)
273 {
274 Cookie cookie = super.access(session, secure);
275 Log.debug("Accessed session {} with id {}", session, session.getId());
276 return cookie;
277 }
278
279 @Override
280 public void complete(HttpSession session)
281 {
282 super.complete(session);
283 Log.debug("Completed session {} with id {}", session, session.getId());
284 }
285
286 protected void removeSession(String clusterId)
287 {
288
289
290
291
292
293
294
295
296 Session session = _sessions.remove(clusterId);
297 Log.debug("Removed session {} with id {}", session, clusterId);
298
299
300
301 SessionData sessionData = _sessionDatas.remove(clusterId);
302 Log.debug("Removed session data {} with id {}", sessionData, clusterId);
303
304
305 _sessionExpirations.remove(clusterId);
306 }
307
308 public void setScavengePeriodMs(long ms)
309 {
310 ms = ms == 0 ? 60000: ms;
311 ms = ms > 60000 ? 60000: ms;
312 ms = ms < 1000 ? 1000: ms;
313 this._scavengePeriodMs = ms;
314 scheduleScavenging();
315 }
316
317 public long getScavengePeriodMs()
318 {
319 return _scavengePeriodMs;
320 }
321
322 public AbstractSessionManager.Session getSession(String clusterId)
323 {
324 Session result = null;
325
326
327
328
329
330
331
332
333 enter(clusterId);
334 try
335 {
336
337
338
339
340 synchronized (_sessions)
341 {
342 result = _sessions.get(clusterId);
343 if (result == null)
344 {
345 Log.debug("Session with id {} --> local cache miss", clusterId);
346
347
348
349
350
351
352
353
354
355
356 Log.debug("Distributed session data with id {} --> lookup", clusterId);
357 SessionData sessionData = _sessionDatas.get(clusterId);
358 if (sessionData == null)
359 {
360 Log.debug("Distributed session data with id {} --> not found", clusterId);
361 }
362 else
363 {
364 Log.debug("Distributed session data with id {} --> found", clusterId);
365
366 result = new Session(sessionData);
367 _sessions.put(clusterId, result);
368 }
369 }
370 else
371 {
372 Log.debug("Session with id {} --> local cache hit", clusterId);
373 if (!_sessionExpirations.containsKey(clusterId))
374 {
375
376
377 _sessions.remove(clusterId);
378 result = null;
379 Log.debug("Session with id {} --> local cache stale");
380 }
381 }
382 }
383 }
384 finally
385 {
386
387
388
389 exit(clusterId);
390 }
391 return result;
392 }
393
394 protected String newLockId(String clusterId)
395 {
396 StringBuilder builder = new StringBuilder(clusterId);
397 builder.append(":").append(_contextPath);
398 builder.append(":").append(_virtualHost);
399 return builder.toString();
400 }
401
402
403 public Map getSessionMap()
404 {
405 return Collections.unmodifiableMap(_sessions);
406 }
407
408
409
410 public int getSessions()
411 {
412 return _sessions.size();
413 }
414
415 protected Session newSession(HttpServletRequest request)
416 {
417
418
419
420
421
422
423 Session result = new Session(request);
424
425 String requestedSessionId = request.getRequestedSessionId();
426 if (requestedSessionId == null)
427 {
428
429 enter(result.getClusterId());
430 }
431 else
432 {
433 if (result.getClusterId().equals(getIdManager().getClusterId(requestedSessionId)))
434 {
435
436
437
438
439 }
440 else
441 {
442
443
444 enter(result.getClusterId());
445 }
446 }
447 return result;
448 }
449
450 protected void invalidateSessions()
451 {
452
453
454
455
456
457
458 }
459
460 private void scavenge()
461 {
462 Thread thread = Thread.currentThread();
463 ClassLoader old_loader = thread.getContextClassLoader();
464 if (_loader != null) thread.setContextClassLoader(_loader);
465 try
466 {
467 long now = System.currentTimeMillis();
468 Log.debug(this + " scavenging at {}, scavenge period {}", now, getScavengePeriodMs());
469
470
471 Set<String> candidates = new HashSet<String>();
472 String lockId = "scavenge:" + _contextPath + ":" + _virtualHost;
473 Lock.lock(lockId);
474 try
475 {
476
477
478
479
480
481
482 synchronized (_sessions)
483 {
484 synchronized (_sessionExpirations)
485 {
486
487
488 Enumeration<String> keys = _sessionExpirations.keys();
489 while (keys.hasMoreElements())
490 {
491 String sessionId = keys.nextElement();
492 MutableLong value = _sessionExpirations.get(sessionId);
493 if (value != null)
494 {
495 long expirationTime = value.value;
496 Log.debug("Estimated expiration time {} for session {}", expirationTime, sessionId);
497 if (expirationTime > 0 && expirationTime < now) candidates.add(sessionId);
498 }
499 }
500
501 _sessions.keySet().retainAll(Collections.list(_sessionExpirations.keys()));
502 }
503 }
504 }
505 finally
506 {
507 Lock.unlock(lockId);
508 }
509 Log.debug("Scavenging detected {} candidate sessions to expire", candidates.size());
510
511
512
513 for (String sessionId : candidates)
514 {
515 Session candidate = (Session)getSession(sessionId);
516 if (candidate == null)
517 continue;
518
519
520 boolean entered = tryEnter(sessionId);
521 if (entered)
522 {
523 try
524 {
525 long maxInactiveTime = candidate.getMaxIdlePeriodMs();
526
527 if (maxInactiveTime > 0)
528 {
529
530 long lastAccessedTime = candidate.getLastAccessedTime();
531
532
533 long expirationTime = lastAccessedTime + maxInactiveTime + getScavengePeriodMs();
534 if (expirationTime < now)
535 {
536 Log.debug("Scavenging expired session {}, expirationTime {}", candidate.getClusterId(), expirationTime);
537
538 candidate.timeout();
539 }
540 else
541 {
542 Log.debug("Scavenging skipping candidate session {}, expirationTime {}", candidate.getClusterId(), expirationTime);
543 }
544 }
545 }
546 finally
547 {
548 exit(sessionId);
549 }
550 }
551 }
552
553 int sessionCount = getSessions();
554 if (sessionCount < _minSessions) _minSessions = sessionCount;
555 if (sessionCount > _maxSessions) _maxSessions = sessionCount;
556 }
557 catch (Throwable x)
558 {
559
560 if(x instanceof ThreadDeath)
561 throw (ThreadDeath)x;
562 Log.warn("Problem scavenging sessions", x);
563 }
564 finally
565 {
566 thread.setContextClassLoader(old_loader);
567 }
568 }
569
570 private String canonicalize(String contextPath)
571 {
572 if (contextPath == null) return "";
573 return contextPath.replace('/', '_').replace('.', '_').replace('\\', '_');
574 }
575
576 private String virtualHostFrom(ContextHandler.SContext context)
577 {
578 String result = "0.0.0.0";
579 if (context == null) return result;
580
581 String[] vhosts = context.getContextHandler().getVirtualHosts();
582 if (vhosts == null || vhosts.length == 0 || vhosts[0] == null) return result;
583
584 return vhosts[0];
585 }
586
587 class Session extends AbstractSessionManager.Session
588 {
589 private static final long serialVersionUID = -2134521374206116367L;
590
591 private final SessionData _sessionData;
592 private long _lastUpdate;
593
594 protected Session(HttpServletRequest request)
595 {
596 super(request);
597 _sessionData = new SessionData(getClusterId(), _maxIdleMs);
598 _lastAccessed = _sessionData.getCreationTime();
599 }
600
601 protected Session(SessionData sd)
602 {
603 super(sd.getCreationTime(), sd.getId());
604 _sessionData = sd;
605 _lastAccessed = getLastAccessedTime();
606 initValues();
607 }
608
609 public SessionData getSessionData()
610 {
611 return _sessionData;
612 }
613
614 @Override
615 public long getCookieSetTime()
616 {
617 return _sessionData.getCookieTime();
618 }
619
620 @Override
621 protected void cookieSet()
622 {
623 _sessionData.setCookieTime(getLastAccessedTime());
624 }
625
626 @Override
627 public void setMaxInactiveInterval(int secs)
628 {
629 super.setMaxInactiveInterval(secs);
630 if(_maxIdleMs > 0L && _maxIdleMs / 10L < (long)_scavengePeriodMs)
631 {
632 long newScavengeSecs = (secs + 9) / 10;
633 setScavengePeriodMs(1000L * newScavengeSecs);
634 }
635
636
637 if (secs < 0) {
638 this._sessionData._expiration.value = -1L;
639 } else {
640 this._sessionData._expiration.value = System.currentTimeMillis() + (1000L * secs);
641 }
642 }
643
644 @Override
645 public long getLastAccessedTime()
646 {
647 if (!isValid()) throw new IllegalStateException();
648 return _sessionData.getPreviousAccessTime();
649 }
650
651 @Override
652 public long getCreationTime() throws IllegalStateException
653 {
654 if (!isValid()) throw new IllegalStateException();
655 return _sessionData.getCreationTime();
656 }
657
658
659 @Override
660 protected String getClusterId()
661 {
662 return super.getClusterId();
663 }
664
665 protected Map newAttributeMap()
666 {
667
668
669
670 return _sessionData.getAttributeMap();
671 }
672
673 @Override
674 protected void access(long time)
675 {
676
677
678
679
680
681
682 long previousAccessTime = getPreviousAccessTime();
683 if (time - previousAccessTime > getScavengePeriodMs())
684 {
685 Log.debug("Out-of-date update of distributed access times: previous {} - current {}", previousAccessTime, time);
686 updateAccessTimes(time);
687 }
688 else
689 {
690 if (time - _lastUpdate > getScavengePeriodMs())
691 {
692 Log.debug("Periodic update of distributed access times: last update {} - current {}", _lastUpdate, time);
693 updateAccessTimes(time);
694 }
695 else
696 {
697 Log.debug("Skipping update of distributed access times: previous {} - current {}", previousAccessTime, time);
698 }
699 }
700 super.access(time);
701 }
702
703
704
705
706
707
708 private void updateAccessTimes(long time)
709 {
710 _sessionData.setPreviousAccessTime(_accessed);
711 if (getMaxIdlePeriodMs() > 0) _sessionData.setExpirationTime(time + getMaxIdlePeriodMs());
712 _lastUpdate = time;
713 }
714
715
716 @Override
717 protected void timeout()
718 {
719 super.timeout();
720 Log.debug("Timed out session {} with id {}", this, getClusterId());
721 }
722
723 @Override
724 public void invalidate()
725 {
726 super.invalidate();
727 Log.debug("Invalidated session {} with id {}", this, getClusterId());
728 }
729
730 private long getMaxIdlePeriodMs()
731 {
732 return _maxIdleMs;
733 }
734
735 private long getPreviousAccessTime()
736 {
737 return super.getLastAccessedTime();
738 }
739 }
740
741
742
743
744 public static class SessionData
745 {
746 private final String _id;
747 private final Map _attributes;
748 private final long _creation;
749 private final MutableLong _expiration;
750 private long _previousAccess;
751 private long _cookieTime;
752
753 public SessionData(String sessionId, long maxIdleMs)
754 {
755 _id = sessionId;
756
757
758 _attributes = new HashMap();
759 _creation = System.currentTimeMillis();
760 _expiration = new MutableLong();
761 _previousAccess = _creation;
762
763 _expiration.value = maxIdleMs > 0 ? _creation + maxIdleMs : -1L;
764 }
765
766 public String getId()
767 {
768 return _id;
769 }
770
771 protected Map getAttributeMap()
772 {
773 return _attributes;
774 }
775
776 public long getCreationTime()
777 {
778 return _creation;
779 }
780
781 public long getExpirationTime()
782 {
783 return _expiration.value;
784 }
785
786 public void setExpirationTime(long time)
787 {
788 _expiration.value = time;
789 }
790
791 public long getCookieTime()
792 {
793 return _cookieTime;
794 }
795
796 public void setCookieTime(long time)
797 {
798 _cookieTime = time;
799 }
800
801 public long getPreviousAccessTime()
802 {
803 return _previousAccess;
804 }
805
806 public void setPreviousAccessTime(long time)
807 {
808 _previousAccess = time;
809 }
810 }
811
812 protected static class Lock
813 {
814 private static final ThreadLocal<Map<String, Integer>> nestings = new ThreadLocal<Map<String, Integer>>()
815 {
816 @Override
817 protected Map<String, Integer> initialValue()
818 {
819 return new HashMap<String, Integer>();
820 }
821 };
822
823 private Lock()
824 {
825 }
826
827 public static void lock(String lockId)
828 {
829 Integer nestingLevel = nestings.get().get(lockId);
830 if (nestingLevel == null) nestingLevel = 0;
831 if (nestingLevel < 0)
832 throw new AssertionError("Lock(" + lockId + ") nest level = " + nestingLevel + ", thread " + Thread.currentThread() + ": " + getLocks());
833 if (nestingLevel == 0)
834 {
835 ManagerUtil.beginLock(lockId, Manager.LOCK_TYPE_WRITE);
836 Log.debug("Lock({}) acquired by thread {}", lockId, Thread.currentThread().getName());
837 }
838 nestings.get().put(lockId, nestingLevel + 1);
839 Log.debug("Lock({}) nestings {}", lockId, getLocks());
840 }
841
842 public static boolean tryLock(String lockId)
843 {
844 boolean result = ManagerUtil.tryBeginLock(lockId, Manager.LOCK_TYPE_WRITE);
845 Log.debug("Lock({}) tried and" + (result ? "" : " not") + " acquired by thread {}", lockId, Thread.currentThread().getName());
846 if (result)
847 {
848 Integer nestingLevel = nestings.get().get(lockId);
849 if (nestingLevel == null) nestingLevel = 0;
850 nestings.get().put(lockId, nestingLevel + 1);
851 Log.debug("Lock({}) nestings {}", lockId, getLocks());
852 }
853 return result;
854 }
855
856 public static void unlock(String lockId)
857 {
858 Integer nestingLevel = nestings.get().get(lockId);
859 if (nestingLevel == null) return;
860 if (nestingLevel < 1)
861 throw new AssertionError("Lock(" + lockId + ") nest level = " + nestingLevel + ", thread " + Thread.currentThread() + ": " + getLocks());
862 if (nestingLevel == 1)
863 {
864 ManagerUtil.commitLock(lockId);
865 Log.debug("Lock({}) released by thread {}", lockId, Thread.currentThread().getName());
866 nestings.get().remove(lockId);
867 }
868 else
869 {
870 nestings.get().put(lockId, nestingLevel - 1);
871 }
872 Log.debug("Lock({}) nestings {}", lockId, getLocks());
873 }
874
875
876
877
878
879 protected static Map<String, Integer> getLocks()
880 {
881 return Collections.unmodifiableMap(nestings.get());
882 }
883 }
884
885 private static class MutableLong
886 {
887 private long value;
888 }
889 }