1 package org.mortbay.terracotta.servlet;
2
3 import java.util.Collections;
4 import java.util.HashMap;
5 import java.util.HashSet;
6 import java.util.Map;
7 import java.util.Set;
8 import java.util.concurrent.Executors;
9 import java.util.concurrent.ScheduledExecutorService;
10 import java.util.concurrent.ScheduledFuture;
11 import java.util.concurrent.TimeUnit;
12
13 import javax.servlet.http.HttpServletRequest;
14
15 import com.tc.object.bytecode.Manager;
16 import com.tc.object.bytecode.ManagerUtil;
17 import org.mortbay.jetty.handler.ContextHandler;
18 import org.mortbay.jetty.servlet.AbstractSessionManager;
19 import org.mortbay.log.Log;
20
21
22
23
24
25
26
27 public class TerracottaSessionManager extends AbstractSessionManager implements Runnable
28 {
29
30
31
32 private Map<String, Session> _sessions;
33
34
35
36
37
38
39
40 private Map<String, SessionData> _sessionData;
41
42
43
44
45
46 private Map<String, MutableLong> _sessionExpirations;
47
48 private long _scavengePeriodMs = 30000;
49 private ScheduledExecutorService _scheduler;
50 private ScheduledFuture<?> _scavenger;
51
52 public void doStart() throws Exception
53 {
54 super.doStart();
55
56 _sessions = Collections.synchronizedMap(new HashMap<String, Session>());
57 _sessionData = newSharedMap("sessionData:" + canonicalize(_context.getContextPath()) + ":" + virtualHostFrom(_context));
58 _sessionExpirations = newSharedMap("sessionExpirations:" + canonicalize(_context.getContextPath()) + ":" + virtualHostFrom(_context));
59 _scheduler = Executors.newSingleThreadScheduledExecutor();
60 scheduleScavenging();
61 }
62
63 private Map newSharedMap(String name)
64 {
65
66
67 final Lock lock = new Lock(name);
68 lock.lock();
69 try
70 {
71 return (Map)ManagerUtil.lookupOrCreateRootNoDepth(name, Collections.synchronizedMap(new HashMap()));
72 }
73 finally
74 {
75 lock.unlock();
76 }
77 }
78
79 private void scheduleScavenging()
80 {
81 if (_scavenger != null)
82 {
83 _scavenger.cancel(true);
84 _scavenger = null;
85 }
86 long scavengePeriod = getScavengePeriodMs();
87 if (scavengePeriod > 0 && _scheduler != null)
88 _scavenger = _scheduler.scheduleWithFixedDelay(this, scavengePeriod, scavengePeriod, TimeUnit.MILLISECONDS);
89 }
90
91 public void doStop() throws Exception
92 {
93 if (_scavenger != null) _scavenger.cancel(true);
94 if (_scheduler != null) _scheduler.shutdownNow();
95 super.doStop();
96 }
97
98 public void run()
99 {
100 scavenge();
101 }
102
103 protected void addSession(AbstractSessionManager.Session session)
104 {
105 String clusterId = getClusterId(session);
106 Session tcSession = (Session)session;
107 SessionData sessionData = tcSession.getSessionData();
108 _sessionExpirations.put(clusterId, sessionData._expiration);
109 _sessionData.put(clusterId, sessionData);
110 _sessions.put(clusterId, tcSession);
111 Log.debug("Added session {} with id {}", tcSession, clusterId);
112 }
113
114 protected void removeSession(String clusterId)
115 {
116
117 Object o = _sessions.remove(clusterId);
118 Log.debug("Removed session {} with id {}", o, clusterId);
119
120
121 o = _sessionData.remove(clusterId);
122 _sessionExpirations.remove(clusterId);
123 Log.debug("Removed session data {} with id {}", o, clusterId);
124 }
125
126 public void setScavengePeriodMs(long ms)
127 {
128 this._scavengePeriodMs = ms;
129 scheduleScavenging();
130 }
131
132 public long getScavengePeriodMs()
133 {
134 return _scavengePeriodMs;
135 }
136
137 public AbstractSessionManager.Session getSession(String clusterId)
138 {
139
140 synchronized (_sessions)
141 {
142 Session session = _sessions.get(clusterId);
143 if (session == null)
144 {
145 Log.debug("Session with id {} --> local cache miss", clusterId);
146
147
148
149
150
151
152
153
154
155
156 Log.debug("Distributed session data with id {} --> lookup", clusterId);
157 SessionData sessionData = _sessionData.get(clusterId);
158 if (sessionData == null)
159 {
160 Log.debug("Distributed session data with id {} --> not found", clusterId);
161 return null;
162 }
163 else
164 {
165 Log.debug("Distributed session data with id {} --> found", clusterId);
166
167 session = new Session(sessionData);
168 _sessions.put(clusterId, session);
169 }
170 }
171 else
172 {
173 Log.debug("Session with id {} --> local cache hit", clusterId);
174 }
175 return session;
176 }
177 }
178
179
180 public Map getSessionMap()
181 {
182 return Collections.unmodifiableMap(_sessions);
183 }
184
185
186
187 public int getSessions()
188 {
189 return _sessions.size();
190 }
191
192 protected Session newSession(HttpServletRequest request)
193 {
194 return new Session(request);
195 }
196
197 protected void invalidateSessions()
198 {
199
200
201
202
203
204
205 }
206
207 private void scavenge()
208 {
209 Thread thread = Thread.currentThread();
210 ClassLoader old_loader = thread.getContextClassLoader();
211 if (_loader != null) thread.setContextClassLoader(_loader);
212 try
213 {
214 long now = System.currentTimeMillis();
215 Log.debug(this + " scavenging at {}, scavenge period {}", now, getScavengePeriodMs());
216
217
218 Set<String> candidates = new HashSet<String>();
219 final Lock lock = new Lock("scavenge:" + canonicalize(_context.getContextPath()) + ":" + virtualHostFrom(_context));
220 lock.lock();
221 try
222 {
223 for (Map.Entry<String, MutableLong> entry : _sessionExpirations.entrySet())
224 {
225 String sessionId = entry.getKey();
226 long expirationTime = entry.getValue().value;
227 Log.debug("Estimated expiration time {} for session {}", expirationTime, sessionId);
228 if (expirationTime > 0 && expirationTime < now) candidates.add(sessionId);
229 }
230 }
231 finally
232 {
233 lock.unlock();
234 }
235 Log.debug("Scavenging detected {} candidate sessions to expire", candidates.size());
236
237
238
239 for (String sessionId : candidates)
240 {
241 Session candidate = (Session)getSession(sessionId);
242
243 candidate.lock();
244 try
245 {
246 long maxInactiveTime = candidate.getMaxIdlePeriodMs();
247
248 if (maxInactiveTime > 0)
249 {
250
251 long lastAccessedTime = candidate.getLastAccessedTime();
252
253
254 long expirationTime = lastAccessedTime + maxInactiveTime + getScavengePeriodMs();
255 if (expirationTime < now)
256 {
257 Log.debug("Scavenging expired session {}, expirationTime {}", candidate.getClusterId(), expirationTime);
258
259 candidate.timeout();
260 }
261 else
262 {
263 Log.debug("Scavenging skipping candidate session {}, expirationTime {}", candidate.getClusterId(), expirationTime);
264 }
265 }
266 }
267 finally
268 {
269 candidate.unlock();
270 }
271 }
272
273 int sessionCount = getSessions();
274 if (sessionCount < _minSessions) _minSessions = sessionCount;
275 if (sessionCount > _maxSessions) _maxSessions = sessionCount;
276 }
277 finally
278 {
279 thread.setContextClassLoader(old_loader);
280 }
281 }
282
283 private String canonicalize(String contextPath)
284 {
285 if (contextPath == null) return "";
286 return contextPath.replace('/', '_').replace('.', '_').replace('\\', '_');
287 }
288
289 private String virtualHostFrom(ContextHandler.SContext context)
290 {
291 String result = "0.0.0.0";
292 if (context == null) return result;
293
294 String[] vhosts = context.getContextHandler().getVirtualHosts();
295 if (vhosts==null || vhosts.length == 0 || vhosts[0] == null) return result;
296
297 return vhosts[0];
298 }
299
300 class Session extends AbstractSessionManager.Session
301 {
302 private static final long serialVersionUID = -2134521374206116367L;
303
304 private final SessionData _sessionData;
305 private final Lock _lock;
306 private long _lastUpdate;
307
308 protected Session(HttpServletRequest request)
309 {
310 super(request);
311 String lockId = new StringBuilder(getClusterId()).
312 append(":").
313 append(canonicalize(_context.getContextPath())).
314 append(":").
315 append(virtualHostFrom(_context)).
316 toString();
317 _sessionData = new SessionData(getClusterId(), lockId, _maxIdleMs);
318 _lastAccessed = _sessionData.getCreationTime();
319 _lock = new Lock(_sessionData.getLockId());
320
321
322 lock();
323 }
324
325 protected Session(SessionData sd)
326 {
327 super(sd.getCreationTime(), sd.getId());
328 _sessionData = sd;
329 _lastAccessed = getLastAccessedTime();
330 _lock = new Lock(sd.getLockId());
331 initValues();
332 }
333
334 public SessionData getSessionData()
335 {
336 return _sessionData;
337 }
338
339 public long getCookieSetTime()
340 {
341 return _sessionData.getCookieTime();
342 }
343
344 protected void cookieSet()
345 {
346 _sessionData.setCookieTime(getLastAccessedTime());
347 }
348
349 public long getLastAccessedTime()
350 {
351 if (!isValid()) throw new IllegalStateException();
352 return _sessionData.getPreviousAccessTime();
353 }
354
355 public long getCreationTime() throws IllegalStateException
356 {
357 if (!isValid()) throw new IllegalStateException();
358 return _sessionData.getCreationTime();
359 }
360
361
362 protected String getClusterId()
363 {
364 return super.getClusterId();
365 }
366
367 protected Map newAttributeMap()
368 {
369
370
371
372 return _sessionData.getAttributeMap();
373 }
374
375 protected void access(long time)
376 {
377
378
379 lock();
380
381
382
383
384
385
386
387 long previousAccessTime = getPreviousAccessTime();
388 if (time - previousAccessTime > getScavengePeriodMs())
389 {
390 Log.debug("Out-of-date update of distributed access times: previous {} - current {}", previousAccessTime, time);
391 updateAccessTimes(time);
392 }
393 else
394 {
395 if (time - _lastUpdate > getScavengePeriodMs())
396 {
397 Log.debug("Periodic update of distributed access times: last update {} - current {}", _lastUpdate, time);
398 updateAccessTimes(time);
399 }
400 else
401 {
402 Log.debug("Skipping update of distributed access times: previous {} - current {}", previousAccessTime, time);
403 }
404 }
405 super.access(time);
406 }
407
408 protected void complete()
409 {
410
411
412 super.complete();
413 unlock();
414 }
415
416
417
418
419
420 private void updateAccessTimes(long time)
421 {
422 _sessionData.setPreviousAccessTime(_accessed);
423 if (getMaxIdlePeriodMs() > 0) _sessionData.setExpirationTime(time + getMaxIdlePeriodMs());
424 _lastUpdate = time;
425 }
426
427
428 protected void timeout()
429 {
430 super.timeout();
431 }
432
433 protected void lock()
434 {
435 _lock.lock();
436 }
437
438 protected void unlock()
439 {
440 _lock.unlock();
441 }
442
443 private long getMaxIdlePeriodMs()
444 {
445 return _maxIdleMs;
446 }
447
448 private long getPreviousAccessTime()
449 {
450 return super.getLastAccessedTime();
451 }
452 }
453
454
455
456
457 public static class SessionData
458 {
459 private final String _id;
460 private final String _lockId;
461 private final Map _attributes;
462 private final long _creation;
463 private final MutableLong _expiration;
464 private long _previousAccess;
465 private long _cookieTime;
466
467 public SessionData(String sessionId, String lockId, long maxIdleMs)
468 {
469 _id = sessionId;
470 _lockId = lockId;
471
472
473 _attributes = new HashMap();
474 _creation = System.currentTimeMillis();
475 _expiration = new MutableLong();
476
477 _expiration.value = maxIdleMs > 0 ? _creation + maxIdleMs : -1L;
478 }
479
480 public String getId()
481 {
482 return _id;
483 }
484
485 public String getLockId()
486 {
487 return _lockId;
488 }
489
490 protected Map getAttributeMap()
491 {
492 return _attributes;
493 }
494
495 public long getCreationTime()
496 {
497 return _creation;
498 }
499
500 public long getExpirationTime()
501 {
502 return _expiration.value;
503 }
504
505 public void setExpirationTime(long time)
506 {
507 _expiration.value = time;
508 }
509
510 public long getCookieTime()
511 {
512 return _cookieTime;
513 }
514
515 public void setCookieTime(long time)
516 {
517 _cookieTime = time;
518 }
519
520 public long getPreviousAccessTime()
521 {
522 return _previousAccess;
523 }
524
525 public void setPreviousAccessTime(long time)
526 {
527 _previousAccess = time;
528 }
529 }
530
531 private static class Lock
532 {
533 private final String _id;
534
535 public Lock(String id)
536 {
537 this._id = id;
538 }
539
540 public void lock()
541 {
542 Log.debug("Locking lock({}) by thread {}", _id, Thread.currentThread().getName());
543 ManagerUtil.beginLock(_id, Manager.LOCK_TYPE_WRITE);
544 }
545
546 public void unlock()
547 {
548 Log.debug("Unlocking lock({}) by thread {}", _id, Thread.currentThread().getName());
549 ManagerUtil.commitLock(_id);
550 }
551 }
552
553 private static class MutableLong
554 {
555 private long value;
556 }
557 }