📄 terracottasessionmanager.java
字号:
// It may happen that one node removes its expired session data, // so that when this node does the same, the session data is already gone SessionData sessionData = _sessionDatas.remove(clusterId); Log.debug("Removed session data {} with id {}", sessionData, clusterId); // Remove the expiration entry used in scavenging _sessionExpirations.remove(clusterId); } public void setScavengePeriodMs(long ms) { ms = ms == 0 ? 60000: ms; ms = ms > 60000 ? 60000: ms; ms = ms < 1000 ? 1000: ms; this._scavengePeriodMs = ms; scheduleScavenging(); } public long getScavengePeriodMs() { return _scavengePeriodMs; } public AbstractSessionManager.Session getSession(String clusterId) { Session result = null; /** * SESSION LOCKING * This is an entry point for session locking. * We lookup the session given the id, and if it exist we hold the lock. * We unlock on end of method, since this method can be called outside * an {@link #enter(String)}/{@link #exit(String)} pair. */ enter(clusterId); try { // Need to synchronize because we use a get-then-put that must be atomic // on the local session cache // Refer to method {@link #scavenge()} for an explanation of synchronization order: // first on _sessions, then on _sessionExpirations. synchronized (_sessions) { result = _sessions.get(clusterId); if (result == null) { Log.debug("Session with id {} --> local cache miss", clusterId); // Lookup the distributed shared sessionData object. // This will migrate the session data to this node from the Terracotta server // We have not grabbed the distributed lock associated with this session yet, // so another node can migrate the session data as well. This is no problem, // since just after this method returns the distributed lock will be grabbed by // one node, the session data will be changed and the lock released. // The second node contending for the distributed lock will then acquire it, // and the session data information will be migrated lazily by Terracotta means. // We are only interested in having a SessionData reference locally. Log.debug("Distributed session data with id {} --> lookup", clusterId); SessionData sessionData = _sessionDatas.get(clusterId); if (sessionData == null) { Log.debug("Distributed session data with id {} --> not found", clusterId); } else { Log.debug("Distributed session data with id {} --> found", clusterId); // Wrap the migrated session data and cache the Session object result = new Session(sessionData); _sessions.put(clusterId, result); } } else { Log.debug("Session with id {} --> local cache hit", clusterId); if (!_sessionExpirations.containsKey(clusterId)) { // A session is present in the local cache, but it has been expired // or invalidated on another node, perform local clean up. _sessions.remove(clusterId); result = null; Log.debug("Session with id {} --> local cache stale"); } } } } finally { /** * SESSION LOCKING */ exit(clusterId); } return result; } protected String newLockId(String clusterId) { StringBuilder builder = new StringBuilder(clusterId); builder.append(":").append(_contextPath); builder.append(":").append(_virtualHost); return builder.toString(); } // TODO: This method is not needed, only used for testing public Map getSessionMap() { return Collections.unmodifiableMap(_sessions); } // TODO: rename to getSessionsCount() // TODO: also, not used if not by superclass for unused statistics data public int getSessions() { return _sessions.size(); } protected Session newSession(HttpServletRequest request) { /** * SESSION LOCKING * This is an entry point for session locking. * We arrive here when we have to create a new * session, for a request.getSession(true) call. */ Session result = new Session(request); String requestedSessionId = request.getRequestedSessionId(); if (requestedSessionId == null) { // Here the user requested a fresh new session, lock it. enter(result.getClusterId()); } else { if (result.getClusterId().equals(getIdManager().getClusterId(requestedSessionId))) { // Here we have a cross context dispatch where the same session id // is used for two different sessions; we do not lock because the lock // has already been acquired in enter(Request), based on the requested // session id. } else { // Here the requested session id is invalid (the session expired), // and a new session is created, lock it. enter(result.getClusterId()); } } return result; } protected void invalidateSessions() { // Do nothing. // We don't want to remove and invalidate all the sessions, // because this method is called from doStop(), and just // because this context is stopping does not mean that we // should remove the session from any other node (remember // the session map is shared) } private void scavenge() { Thread thread = Thread.currentThread(); ClassLoader old_loader = thread.getContextClassLoader(); if (_loader != null) thread.setContextClassLoader(_loader); try { long now = System.currentTimeMillis(); Log.debug(this + " scavenging at {}, scavenge period {}", now, getScavengePeriodMs()); // Detect the candidates that may have expired already, checking the estimated expiration time. Set<String> candidates = new HashSet<String>(); String lockId = "scavenge:" + _contextPath + ":" + _virtualHost; Lock.lock(lockId); try { /** * Synchronize in order, to avoid deadlocks with method {@link #getSession(String)}. * In that method, we first synchronize on _session, then we call _sessionExpirations.containsKey(), * which is synchronized by virtue of being a Collection.synchronizedMap. * Here we must synchronize in the same order to avoid deadlock. */ synchronized (_sessions) { synchronized (_sessionExpirations) { // Do not use iterators that throw ConcurrentModificationException // We do a best effort here, and leave possible imprecisions to the next scavenge Enumeration<String> keys = _sessionExpirations.keys(); while (keys.hasMoreElements()) { String sessionId = keys.nextElement(); MutableLong value = _sessionExpirations.get(sessionId); if (value != null) { long expirationTime = value.value; Log.debug("Estimated expiration time {} for session {}", expirationTime, sessionId); if (expirationTime > 0 && expirationTime < now) candidates.add(sessionId); } } _sessions.keySet().retainAll(Collections.list(_sessionExpirations.keys())); } } } finally { Lock.unlock(lockId); } Log.debug("Scavenging detected {} candidate sessions to expire", candidates.size()); // Now validate that the candidates that do expire are really expired, // grabbing the session lock for each candidate for (String sessionId : candidates) { Session candidate = (Session)getSession(sessionId); if (candidate == null) continue; // Here we grab the lock to avoid anyone else interfering boolean entered = tryEnter(sessionId); if (entered) { try { long maxInactiveTime = candidate.getMaxIdlePeriodMs(); // Exclude sessions that never expire if (maxInactiveTime > 0) { // The lastAccessedTime is fetched from Terracotta, so we're sure it is up-to-date. long lastAccessedTime = candidate.getLastAccessedTime(); // Since we write the shared lastAccessedTime every scavenge period, // take that in account before considering the session expired long expirationTime = lastAccessedTime + maxInactiveTime + getScavengePeriodMs(); if (expirationTime < now) { Log.debug("Scavenging expired session {}, expirationTime {}", candidate.getClusterId(), expirationTime); // Calling timeout() result in calling removeSession(), that will clean the data structures candidate.timeout(); } else { Log.debug("Scavenging skipping candidate session {}, expirationTime {}", candidate.getClusterId(), expirationTime); } } } finally { exit(sessionId); } } } int sessionCount = getSessions(); if (sessionCount < _minSessions) _minSessions = sessionCount; if (sessionCount > _maxSessions) _maxSessions = sessionCount; } catch (Throwable x) { // Must avoid at all costs that the scavenge thread exits, so here we catch and log if(x instanceof ThreadDeath) throw (ThreadDeath)x; Log.warn("Problem scavenging sessions", x); } finally { thread.setContextClassLoader(old_loader); } } private String canonicalize(String contextPath) { if (contextPath == null) return ""; return contextPath.replace('/', '_').replace('.', '_').replace('\\', '_'); } private String virtualHostFrom(ContextHandler.SContext context) { String result = "0.0.0.0"; if (context == null) return result; String[] vhosts = context.getContextHandler().getVirtualHosts(); if (vhosts == null || vhosts.length == 0 || vhosts[0] == null) return result; return vhosts[0]; } class Session extends AbstractSessionManager.Session { private static final long serialVersionUID = -2134521374206116367L; private final SessionData _sessionData; private long _lastUpdate; protected Session(HttpServletRequest request)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -