/* * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.web.tomcat.service.session; import java.security.AccessController; import java.security.PrivilegedAction; import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.servlet.http.HttpSession; import javax.servlet.sip.SipApplicationSession; import javax.servlet.sip.SipSession; import javax.servlet.sip.SipApplicationSession.Protocol; import org.apache.catalina.Container; import org.apache.catalina.Context; import org.apache.catalina.LifecycleException; import org.apache.catalina.Session; import org.apache.catalina.Valve; import org.apache.catalina.core.ContainerBase; import org.jboss.logging.Logger; import org.jboss.metadata.web.jboss.JBossWebMetaData; import org.jboss.metadata.web.jboss.ReplicationGranularity; import org.jboss.metadata.web.jboss.SnapshotMode; import org.jboss.util.loading.ContextClassLoaderSwitcher; import org.jboss.web.tomcat.service.session.distributedcache.spi.BatchingManager; import org.jboss.web.tomcat.service.session.distributedcache.spi.ClusteringNotSupportedException; import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSessionMetadata; import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheConvergedSipManager; import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManagerFactory; import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManagerFactoryFactory; import org.jboss.web.tomcat.service.session.distributedcache.spi.IncomingDistributableSessionData; import org.jboss.web.tomcat.service.session.distributedcache.spi.OutgoingAttributeGranularitySessionData; import org.jboss.web.tomcat.service.session.distributedcache.spi.OutgoingDistributableSessionData; import org.jboss.web.tomcat.service.session.distributedcache.spi.OutgoingSessionGranularitySessionData; import org.jboss.web.tomcat.service.session.notification.ClusteredSessionNotificationCause; import org.jboss.web.tomcat.service.session.notification.ClusteredSipApplicationSessionNotificationCapability; import org.jboss.web.tomcat.service.session.notification.ClusteredSipApplicationSessionNotificationPolicy; import org.jboss.web.tomcat.service.session.notification.ClusteredSipSessionNotificationCapability; import org.jboss.web.tomcat.service.session.notification.ClusteredSipSessionNotificationPolicy; import org.jboss.web.tomcat.service.session.notification.IgnoreUndeployLegacyClusteredSipApplicationSessionNotificationPolicy; import org.jboss.web.tomcat.service.session.notification.IgnoreUndeployLegacyClusteredSipSessionNotificationPolicy; import org.mobicents.servlet.sip.core.session.MobicentsSipApplicationSession; import org.mobicents.servlet.sip.core.session.MobicentsSipSession; import org.mobicents.servlet.sip.core.session.SessionManagerUtil; import org.mobicents.servlet.sip.core.session.SipApplicationSessionKey; import org.mobicents.servlet.sip.core.session.SipManagerDelegate; import org.mobicents.servlet.sip.core.session.SipSessionKey; import org.mobicents.servlet.sip.message.SipFactoryImpl; /** * Implementation of a converged clustered session manager for * catalina using JBossCache replication. * * Based on JbossCacheManager JBOSS AS 5.1.0.GA Tag * * TODO Some parts have been copied over but the request for change to protected was made http://www.jboss.org/index.html?module=bb&op=viewtopic&p=4231452#4231452 * review this for the GA version * * @author Ben Wang * @author Brian Stansberry * @author <A HREF="mailto:jean.deruelle@gmail.com">Jean Deruelle</A> * */ public class JBossCacheSipManager<O extends OutgoingDistributableSessionData> extends JBossCacheManager implements ClusteredSipManager<O> { protected static final String DISTRIBUTED_CACHE_FACTORY_CLASSNAME = "org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheConvergedSipManagerFactoryImpl"; protected static Logger logger = Logger.getLogger(JBossCacheSipManager.class); protected SipManagerDelegate sipManagerDelegate; protected DistributedCacheManagerFactory distributedCacheManagerFactory; /** * Informational name for this Catalina component */ private static final String info_ = "JBossCacheSipManager/1.0"; // -- Class attributes --------------------------------- /** * Id/timestamp of sessions in cache that we haven't loaded */ protected Map<ClusteredSipSessionKey, OwnedSessionUpdate> unloadedSipSessions_ = new ConcurrentHashMap<ClusteredSipSessionKey, OwnedSessionUpdate>(); /** * Id/timestamp of sessions in cache that we haven't loaded */ protected Map<SipApplicationSessionKey, OwnedSessionUpdate> unloadedSipApplicationSessions_ = new ConcurrentHashMap<SipApplicationSessionKey, OwnedSessionUpdate>(); /** * Id/timestamp of sessions in distributedcache that we haven't loaded * locally */ private Map<String, OwnedSessionUpdate> unloadedSessions_ = new ConcurrentHashMap<String, OwnedSessionUpdate>(); /** Number of passivated sip sessions */ private AtomicInteger sipSessionPassivatedCount_ = new AtomicInteger(); /** Maximum number of concurrently passivated sip sessions */ private AtomicInteger sipSessionMaxPassivatedCount_ = new AtomicInteger(); /** Number of passivated sip applicationsessions */ private AtomicInteger sipApplicationSessionPassivatedCount_ = new AtomicInteger(); /** Maximum number of concurrently passivated sip application sessions */ private AtomicInteger sipApplicationSessionMaxPassivatedCount_ = new AtomicInteger(); private String sipSessionNotificationPolicyClass_; private String sipApplicationSessionNotificationPolicyClass_; private ClusteredSipApplicationSessionNotificationPolicy sipApplicationSessionNotificationPolicy_; private ClusteredSipSessionNotificationPolicy sipSessionNotificationPolicy_; /** Number of passivated sessions */ private AtomicInteger passivatedCount_ = new AtomicInteger(); /** Maximum number of concurrently passivated sessions */ private AtomicInteger maxPassivatedCount_ = new AtomicInteger(); private static final int TOTAL_PERMITS = Integer.MAX_VALUE; private Semaphore semaphore = new Semaphore(TOTAL_PERMITS, true); private Lock valveLock = new SemaphoreLock(this.semaphore); /** Are we running embedded in JBoss? */ private boolean embedded_ = false; // ---------------------------------------------------------- Constructors public JBossCacheSipManager() throws ClusteringNotSupportedException { this(getConvergedDistributedCacheManager()); } protected static DistributedCacheManagerFactory getConvergedDistributedCacheManager() throws ClusteringNotSupportedException { DistributedCacheManagerFactoryFactory distributedCacheManagerFactoryFactory = DistributedCacheManagerFactoryFactory.getInstance(); distributedCacheManagerFactoryFactory.setFactoryClassName(DISTRIBUTED_CACHE_FACTORY_CLASSNAME); return distributedCacheManagerFactoryFactory.getDistributedCacheManagerFactory(); } /** * Create a new JBossCacheManager using the given cache. For use in unit testing. * * @param pojoCache */ public JBossCacheSipManager(DistributedCacheManagerFactory distributedManagerFactory) { super(distributedManagerFactory); } @Override public void init(String name, JBossWebMetaData webMetaData) throws ClusteringNotSupportedException { super.init(name, webMetaData); sipManagerDelegate = new ClusteredSipManagerDelegate(ReplicationGranularity.valueOf(getReplicationGranularityString()),getUseJK(), (ClusteredSipManager)this); this.sipApplicationSessionNotificationPolicyClass_ = getReplicationConfig().getSessionNotificationPolicy(); this.sipSessionNotificationPolicyClass_ = getReplicationConfig().getSessionNotificationPolicy(); embedded_ = true; } private void initClusteredSipSessionNotificationPolicy() { if (this.sipSessionNotificationPolicyClass_ == null || this.sipSessionNotificationPolicyClass_.length() == 0) { this.sipSessionNotificationPolicyClass_ = AccessController.doPrivileged(new PrivilegedAction<String>() { public String run() { return System.getProperty("jboss.web.clustered.session.notification.policy", IgnoreUndeployLegacyClusteredSipSessionNotificationPolicy.class.getName()); } }); } try { this.sipSessionNotificationPolicy_ = (ClusteredSipSessionNotificationPolicy) Thread.currentThread().getContextClassLoader().loadClass(this.sipSessionNotificationPolicyClass_).newInstance(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException("Failed to instantiate " + ClusteredSipSessionNotificationPolicy.class.getName() + " " + this.sipSessionNotificationPolicyClass_, e); } this.sipSessionNotificationPolicy_.setClusteredSipSessionNotificationCapability(new ClusteredSipSessionNotificationCapability()); } private void initClusteredSipApplicationSessionNotificationPolicy() { if (this.sipApplicationSessionNotificationPolicyClass_ == null || this.sipApplicationSessionNotificationPolicyClass_.length() == 0) { this.sipApplicationSessionNotificationPolicyClass_ = AccessController.doPrivileged(new PrivilegedAction<String>() { public String run() { return System.getProperty("jboss.web.clustered.session.notification.policy", IgnoreUndeployLegacyClusteredSipApplicationSessionNotificationPolicy.class.getName()); } }); } try { this.sipApplicationSessionNotificationPolicy_ = (ClusteredSipApplicationSessionNotificationPolicy) Thread.currentThread().getContextClassLoader().loadClass(this.sipApplicationSessionNotificationPolicyClass_).newInstance(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException("Failed to instantiate " + ClusteredSipApplicationSessionNotificationPolicy.class.getName() + " " + this.sipApplicationSessionNotificationPolicyClass_, e); } this.sipApplicationSessionNotificationPolicy_.setClusteredSipApplicationSessionNotificationCapability(new ClusteredSipApplicationSessionNotificationCapability()); } @SuppressWarnings("unchecked") private static ClusteredSession<? extends OutgoingDistributableSessionData> uncheckedCastSession(Session session) { return (ClusteredSession) session; } @SuppressWarnings("unchecked") private static ClusteredSipSession<? extends OutgoingDistributableSessionData> uncheckedCastSipSession(SipSession session) { return (ClusteredSipSession) session; } @SuppressWarnings("unchecked") private static ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData> uncheckedCastSipApplicationSession(SipApplicationSession session) { return (ClusteredSipApplicationSession) session; } @SuppressWarnings("unchecked") private static <T extends OutgoingDistributableSessionData> JBossCacheSipManager<T> uncheckedCastSipManager(JBossCacheSipManager mgr) { return mgr; } @SuppressWarnings("unchecked") public DistributedCacheConvergedSipManager<? extends OutgoingDistributableSessionData> getDistributedCacheConvergedSipManager() { return (DistributedCacheConvergedSipManager) getDistributedCacheManager(); } // Satisfy the Manager interface. Internally we use // createEmptyClusteredSession to avoid a cast public Session createEmptySession() { if (trace_) { log_.trace("Creating an empty ClusteredSession"); } return createEmptyConvergedClusteredSession(); } private ClusteredSession<? extends OutgoingDistributableSessionData> createEmptyConvergedClusteredSession() { ClusteredSession<? extends OutgoingDistributableSessionData> session = null; switch (getReplicationGranularity()) { case ATTRIBUTE: ClusteredSipManager<OutgoingAttributeGranularitySessionData> amgr = uncheckedCastSipManager(this); session = new ConvergedAttributeBasedClusteredSession(amgr); break; case FIELD: ClusteredSipManager<OutgoingDistributableSessionData> fmgr = uncheckedCastSipManager(this); session = new ConvergedFieldBasedClusteredSession(fmgr); break; default: ClusteredSipManager<OutgoingSessionGranularitySessionData> smgr = uncheckedCastSipManager(this); session = new ConvergedSessionBasedClusteredSession(smgr); break; } return session; } /** * {@inheritDoc} */ public long getPassivatedSessionCount() { return passivatedCount_.get(); } /** * {@inheritDoc} */ public long getMaxPassivatedSessionCount() { return maxPassivatedCount_.get(); } /** * {@inheritDoc} */ public Session createSession(String sessionId) { // First check if we've reached the max allowed sessions, // then try to expire/passivate sessions to free memory // maxActiveAllowed_ -1 is unlimited // We check here for maxActive instead of in add(). add() gets called // when we load an already existing session from the distributed cache // (e.g. in a failover) and we don't want to fail in that situation. if(maxActiveAllowed_ != -1 && calcActiveSessions() >= maxActiveAllowed_) { if (trace_) { log_.trace("createSession(): active sessions = " + calcActiveSessions() + " and max allowed sessions = " + maxActiveAllowed_); } processExpirationPassivation(); if (calcActiveSessions() >= maxActiveAllowed_) { // Exceeds limit. We need to reject it. rejectedCounter_.incrementAndGet(); // Catalina api does not specify what happens // but we will throw a runtime exception for now. String msgEnd = (sessionId == null) ? "" : " id " + sessionId; throw new IllegalStateException("createSession(): number of " + "active sessions exceeds the maximum limit: " + maxActiveAllowed_ + " when trying to create session" + msgEnd); } } ClusteredSession<? extends OutgoingDistributableSessionData> session = createEmptyConvergedClusteredSession(); session.setNew(true); session.setCreationTime(System.currentTimeMillis()); session.setMaxInactiveInterval(this.maxInactiveInterval_); session.setValid(true); String clearInvalidated = null; // see below if (sessionId == null) { sessionId = this.getNextId(); // We are using mod_jk for load balancing. Append the JvmRoute. if (getUseJK()) { if (trace_) { log_.trace("createSession(): useJK is true. Will append JvmRoute: " + this.getJvmRoute()); } sessionId += "." + this.getJvmRoute(); } } else { clearInvalidated = sessionId; } session.setId(sessionId); // Setting the id leads to a call to add() getDistributedCacheManager().sessionCreated(session.getRealId()); session.tellNew(ClusteredSessionNotificationCause.CREATE); if (trace_) { log_.trace("Created a ClusteredSession with id: " + sessionId); } createdCounter_.incrementAndGet(); // the call to add() handles the other counters // Add this session to the set of those potentially needing replication ConvergedSessionReplicationContext.bindSession(session, getSnapshotManager()); if (clearInvalidated != null) { // We no longer need to track any earlier session w/ same id // invalidated by this thread SessionInvalidationTracker.clearInvalidatedSession(clearInvalidated, this); } return session; } /** * Loads a session from the distributed store. If an existing session with * the id is already under local management, that session's internal state * will be updated from the distributed store. Otherwise a new session * will be created and added to the collection of those sessions under * local management. * * @param realId id of the session-id with any jvmRoute removed * * @return the session or <code>null</code> if the session cannot be found * in the distributed store */ private ClusteredSession<? extends OutgoingDistributableSessionData> loadSession(String realId) { if (realId == null) { return null; } long begin = System.currentTimeMillis(); boolean mustAdd = false; boolean passivated = false; ClusteredSession<? extends OutgoingDistributableSessionData> session = sessions_.get(realId); boolean initialLoad = false; if (session == null) { initialLoad = true; // This is either the first time we've seen this session on this // server, or we previously expired it and have since gotten // a replication message from another server mustAdd = true; session = createEmptyConvergedClusteredSession(); OwnedSessionUpdate osu = unloadedSessions_.get(realId); passivated = (osu != null && osu.passivated); } synchronized (session) { ContextClassLoaderSwitcher.SwitchContext switcher = null; boolean doTx = false; try { // We need transaction so any data gravitation replication // is sent in batch. // Don't do anything if there is already transaction context // associated with this thread. if (getDistributedCacheConvergedSipManager().getBatchingManager().isBatchInProgress() == false) { getDistributedCacheConvergedSipManager().getBatchingManager().startBatch(); doTx = true; } // Tomcat calls Manager.findSession before setting the tccl, // so we need to do it :( switcher = getContextClassLoaderSwitcher().getSwitchContext(); switcher.setClassLoader(getApplicationClassLoader()); IncomingDistributableSessionData data = getDistributedCacheManager().getSessionData(realId, initialLoad); if (data != null) { session.update(data); } else { // Clunky; we set the session variable to null to indicate // no data so move on session = null; } if (session != null) { ClusteredSessionNotificationCause cause = passivated ? ClusteredSessionNotificationCause.ACTIVATION : ClusteredSessionNotificationCause.FAILOVER; session.notifyDidActivate(cause); } } catch (Exception ex) { try { // if(doTx) // Let's set it no matter what. getDistributedCacheConvergedSipManager().getBatchingManager().setBatchRollbackOnly(); } catch (Exception exn) { log_.error("Caught exception rolling back transaction", exn); } // We will need to alert Tomcat of this exception. if (ex instanceof RuntimeException) throw (RuntimeException) ex; throw new RuntimeException("loadSession(): failed to load session " + realId, ex); } finally { try { if(doTx) { getDistributedCacheConvergedSipManager().getBatchingManager().endBatch(); } } finally { if (switcher != null) { switcher.reset(); } } } if (session != null) { if (mustAdd) { add(session, false); // don't replicate if (!passivated) { session.tellNew(ClusteredSessionNotificationCause.FAILOVER); } } long elapsed = System.currentTimeMillis() - begin; stats_.updateLoadStats(realId, elapsed); if (trace_) { log_.trace("loadSession(): id= " + realId + ", session=" + session); } } else if (trace_) { log_.trace("loadSession(): session " + realId + " not found in distributed cache"); } } return session; } /** * {@inheritDoc} */ public void add(Session session) { if (session == null) return; if (!(session instanceof ClusteredSession)) { throw new IllegalArgumentException("You can only add instances of " + "type ClusteredSession to this Manager. Session class name: " + session.getClass().getName()); } add(uncheckedCastSession(session), false); // wait to replicate until req end } /** * Adds the given session to the collection of those being managed by this * Manager. * * @param session the session. Cannot be <code>null</code>. * @param replicate whether the session should be replicated * * @throws NullPointerException if <code>session</code> is <code>null</code>. */ private void add(ClusteredSession<? extends OutgoingDistributableSessionData> session, boolean replicate) { // TODO -- why are we doing this check? The request checks session // validity and will expire the session; this seems redundant if (!session.isValid()) { // Not an error; this can happen if a failover request pulls in an // outdated session from the distributed cache (see TODO above) log_.debug("Cannot add session with id=" + session.getIdInternal() + " because it is invalid"); return; } String realId = session.getRealId(); Object existing = sessions_.put(realId, session); unloadedSessions_.remove(realId); if (!session.equals(existing)) { if (replicate) { storeSession(session); } // Update counters calcActiveSessions(); if (trace_) { log_.trace("Session with id=" + session.getIdInternal() + " added. " + "Current active sessions " + localActiveCounter_.get()); } } } /** * Return the sessions. Note that this will return not only the local * in-memory sessions, but also any sessions that are in the distributed * cache but have not previously been accessed on this server. Invoking * this method will bring all such sessions into local memory and can * potentially be quite expensive. * * <p> * Note also that when sessions are loaded from the distributed cache, no * check is made as to whether the number of local sessions will thereafter * exceed the maximum number allowed on this server. * </p> * * @return an array of all the sessions */ public Session[] findSessions() { // Need to load all the unloaded sessions if(unloadedSessions_.size() > 0) { // Make a thread-safe copy of the new id list to work with Set<String> ids = new HashSet<String>(unloadedSessions_.keySet()); if(trace_) { log_.trace("findSessions: loading sessions from distributed cache: " + ids); } for(String id : ids) { loadSession(id); } } // All sessions are now "local" so just return the local sessions return findLocalSessions(); } /** * Attempts to find the session in the collection of those being managed * locally, and if not found there, in the distributed cache of sessions. * <p> * If a session is found in the distributed cache, it is added to the * collection of those being managed locally. * </p> * * @param id the session id, which may include an appended jvmRoute * * @return the session, or <code>null</code> if no such session could * be found */ public Session findSession(String id) { String realId = getRealId(id); // Find it from the local store first ClusteredSession<? extends OutgoingDistributableSessionData> session = findLocalSession(realId); // If we didn't find it locally, only check the distributed cache // if we haven't previously handled this session id on this request. // If we handled it previously but it's no longer local, that means // it's been invalidated. If we request an invalidated session from // the distributed cache, it will be missing from the local cache but // may still exist on other nodes (i.e. if the invalidation hasn't // replicated yet because we are running in a tx). With buddy replication, // asking the local cache for the session will cause the out-of-date // session from the other nodes to be gravitated, thus resuscitating // the session. if (session == null && !SessionInvalidationTracker.isSessionInvalidated(realId, this)) { if (trace_) log_.trace("Checking for session " + realId + " in the distributed cache"); session = loadSession(realId); // if (session != null) // { // add(session); // // We now notify, since we've added a policy to allow listeners // // to discriminate. But the default policy will not allow the // // notification to be emitted for FAILOVER, so the standard // // behavior is unchanged. // session.tellNew(ClusteredSessionNotificationCause.FAILOVER); // } } else if (session != null && session.isOutdated()) { if (trace_) log_.trace("Updating session " + realId + " from the distributed cache"); // Need to update it from the cache loadSession(realId); } if (session != null) { // Add this session to the set of those potentially needing replication ConvergedSessionReplicationContext.bindSession(session, getSnapshotManager()); // If we previously called passivate() on the session due to // replication, we need to make an offsetting activate() call if (session.getNeedsPostReplicateActivation()) { session.notifyDidActivate(ClusteredSessionNotificationCause.REPLICATION); } } return session; } /** * Gets the session id with any jvmRoute removed. * * @param id a session id with or without an appended jvmRoute. * Cannot be <code>null</code>. */ private String getRealId(String id) { return (getUseJK() ? Util.getRealId(id) : id); } /** * {@inheritDoc} * <p> * Overrides the superclass version to ensure that the generated id * does not duplicate the id of any other session this manager is aware of. * </p> */ @Override protected String getNextId() { while (true) { String id = super.getNextId(); if (sessions_.containsKey(id) || unloadedSessions_.containsKey(id)) { duplicates_.incrementAndGet(); } else { return id; } } } protected int getTotalActiveSessions() { return localActiveCounter_.get() + unloadedSessions_.size() - passivatedCount_.get(); } @Override public void resetStats() { super.resetStats(); this.maxPassivatedCount_.set(this.passivatedCount_.get()); } /** * Gets the ids of all sessions in the distributed cache and adds * them to the unloaded sessions map, along with their lastAccessedTime * and their maxInactiveInterval. Passivates overage or excess sessions. */ protected void initializeUnloadedSessions() { Map<String, String> sessions = getDistributedCacheManager().getSessionIds(); if (sessions != null) { boolean passivate = isPassivationEnabled(); long passivationMax = passivationMaxIdleTime_ * 1000L; long passivationMin = passivationMinIdleTime_ * 1000L; for (Map.Entry<String, String> entry : sessions.entrySet()) { String realId = entry.getKey(); String owner = entry.getValue(); long ts = -1; DistributableSessionMetadata md = null; try { IncomingDistributableSessionData sessionData = getDistributedCacheManager().getSessionData(realId, owner, false); ts = sessionData.getTimestamp(); md = sessionData.getMetadata(); } catch (Exception e) { // most likely a lock conflict if the session is being updated remotely; // ignore it and use default values for timstamp and maxInactive log_.debug("Problem reading metadata for session " + realId + " -- " + e.toString()); } long lastMod = ts == -1 ? System.currentTimeMillis() : ts; int maxLife = md == null ? getMaxInactiveInterval() : md.getMaxInactiveInterval(); OwnedSessionUpdate osu = new OwnedSessionUpdate(owner, lastMod, maxLife, false); unloadedSessions_.put(realId, osu); if (passivate) { try { long elapsed = System.currentTimeMillis() - lastMod; // if maxIdle time configured, means that we need to passivate sessions that have // exceeded the max allowed idle time if (passivationMax >= 0 && elapsed > passivationMax) { if (trace_) { log_.trace("Elapsed time of " + elapsed + " for session "+ realId + " exceeds max of " + passivationMax + "; passivating"); } processUnloadedSessionPassivation(realId, osu); } // If the session didn't exceed the passivationMaxIdleTime_, see // if the number of sessions managed by this manager greater than the max allowed // active sessions, passivate the session if it exceed passivationMinIdleTime_ else if (maxActiveAllowed_ > 0 && passivationMin >= 0 && calcActiveSessions() > maxActiveAllowed_ && elapsed >= passivationMin) { if (trace_) { log_.trace("Elapsed time of " + elapsed + " for session "+ realId + " exceeds min of " + passivationMin + "; passivating"); } processUnloadedSessionPassivation(realId, osu); } } catch (Exception e) { // most likely a lock conflict if the session is being updated remotely; ignore it log_.debug("Problem passivating session " + realId + " -- " + e.toString()); } } } } } /** * {@inheritDoc} */ public String listSessionIds() { Set<String> ids = new HashSet<String>(sessions_.keySet()); ids.addAll(unloadedSessions_.keySet()); return reportSessionIds(ids); } private String reportSessionIds(Set<String> ids) { StringBuffer sb = new StringBuffer(); boolean added = false; for (String id : ids) { if (added) { sb.append(','); } else { added = true; } sb.append(id); } return sb.toString(); } /** * Notifies the manager that a session in the distributed cache has * been invalidated * * FIXME This method is poorly named, as we will also get this callback * when we use buddy replication and our copy of the session in JBC is being * removed due to data gravitation. * * @param realId the session id excluding any jvmRoute */ public void notifyRemoteInvalidation(String realId) { // Remove the session from our local map ClusteredSession<? extends OutgoingDistributableSessionData> session = sessions_.remove(realId); if (session == null) { // We weren't managing the session anyway. But remove it // from the list of cached sessions we haven't loaded if (unloadedSessions_.remove(realId) != null) { if (trace_) log_.trace("Removed entry for session " + realId + " from unloaded session map"); } // If session has failed over and has been passivated here, // session will be null, but we'll have a TimeStatistic to clean up stats_.removeStats(realId); } else { // Expire the session // DON'T SYNCHRONIZE ON SESSION HERE -- isValid() and // expire() are meant to be multi-threaded and synchronize // properly internally; synchronizing externally can lead // to deadlocks!! boolean notify = false; // Don't notify listeners. SRV.10.7 // allows this, and sending notifications // leads to all sorts of issues; e.g. // circular calls with ClusteredSSO and // notifying when all that's happening is // data gravitation due to random failover boolean localCall = false; // this call originated from the cache; // we have already removed session boolean localOnly = true; // Don't pass attr removals to cache // Ensure the correct TCL is in place // BES 2008/11/27 Why? ContextClassLoaderSwitcher.SwitchContext switcher = null; try { // Don't track this invalidation is if it were from a request SessionInvalidationTracker.suspend(); switcher = getContextClassLoaderSwitcher().getSwitchContext(); switcher.setClassLoader(getApplicationClassLoader()); session.expire(notify, localCall, localOnly, ClusteredSessionNotificationCause.INVALIDATE); } finally { SessionInvalidationTracker.resume(); // Remove any stats for this session stats_.removeStats(realId); if (switcher != null) { switcher.reset(); } } } } /** * {@inheritDoc} */ @Override protected void processExpirationPassivation() { boolean expire = maxInactiveInterval_ >= 0; boolean passivate = isPassivationEnabled(); long passivationMax = passivationMaxIdleTime_ * 1000L; long passivationMin = passivationMinIdleTime_ * 1000L; if (trace_) { log_.trace("processExpirationPassivation(): Looking for sessions that have expired ..."); log_.trace("processExpirationPassivation(): active sessions = " + calcActiveSessions()); log_.trace("processExpirationPassivation(): expired sessions = " + expiredCounter_); if (passivate) { log_.trace("processExpirationPassivation(): passivated count = " + getPassivatedSessionCount()); } } // Holder for sessions or OwnedSessionUpdates that survive expiration, // sorted by last acccessed time TreeSet<PassivationCheck> passivationChecks = new TreeSet<PassivationCheck>(); try { // Don't track sessions invalidated via this method as if they // were going to be re-requested by the thread SessionInvalidationTracker.suspend(); // First, handle the sessions we are actively managing ClusteredSession<? extends OutgoingDistributableSessionData> sessions[] = findLocalSessions(); for (int i = 0; i < sessions.length; ++i) { try { ClusteredSession<? extends OutgoingDistributableSessionData> session = sessions[i]; if(session == null) { log_.warn("processExpirationPassivation(): processing null session at index " +i); continue; } if (expire) { // JBAS-2403. Check for outdated sessions where we think // the local copy has timed out. If found, refresh the // session from the cache in case that might change the timeout if (session.isOutdated() && !(session.isValid(false))) { // FIXME in AS 5 every time we get a notification from the distributed // cache of an update, we get the latest timestamp. So // we shouldn't need to do a full session load here. A load // adds a risk of an unintended data gravitation. // JBAS-2792 don't assign the result of loadSession to session // just update the object from the cache or fall through if // the session has been removed from the cache loadSession(session.getRealId()); } // Do a normal invalidation check that will expire the // session if it has timed out // DON'T SYNCHRONIZE on session here -- isValid() and // expire() are meant to be multi-threaded and synchronize // properly internally; synchronizing externally can lead // to deadlocks!! if (!session.isValid()) continue; } // we now have a valid session; store it so we can check later // if we need to passivate it if (passivate) { passivationChecks.add(new PassivationCheck(session)); } } catch (Exception ex) { log_.error("processExpirationPassivation(): failed handling " + sessions[i].getIdInternal() + " with exception: " + ex, ex); } } // Next, handle any unloaded sessions // We may have not gotten replication of a timestamp for requests // that occurred w/in maxUnreplicatedInterval_ of the previous // request. So we add a grace period to avoid flushing a session early // and permanently losing part of its node structure in JBoss Cache. long maxUnrep = getMaxUnreplicatedInterval() < 0 ? 60 : getMaxUnreplicatedInterval(); Map<String, OwnedSessionUpdate> unloaded = new HashMap<String, OwnedSessionUpdate>(unloadedSessions_); for (Map.Entry<String, OwnedSessionUpdate> entry : unloaded.entrySet()) { String realId = entry.getKey(); OwnedSessionUpdate osu = entry.getValue(); long now = System.currentTimeMillis(); long elapsed = (now - osu.updateTime); try { if (expire && osu.maxInactive >= 1 && elapsed >= (osu.maxInactive + maxUnrep) * 1000L) { //if (osu.passivated && osu.owner == null) if (osu.passivated) { // Passivated session needs to be expired. A call to // findSession will bring it out of passivation Session session = findSession(realId); if (session != null) { session.isValid(); // will expire continue; } } // If we get here either !osu.passivated, or we don't own // the session or the session couldn't be reactivated (invalidated by user). // Either way, do a cleanup getDistributedCacheManager().removeSessionLocal(realId, osu.owner); unloadedSessions_.remove(realId); stats_.removeStats(realId); } else if (passivate && !osu.passivated) { // we now have a valid session; store it so we can check later // if we need to passivate it passivationChecks.add(new PassivationCheck(realId, osu)); } } catch (Exception ex) { log_.error("processExpirationPassivation(): failed handling unloaded session " + realId, ex); } } // Now, passivations if (passivate) { // Iterate through sessions, earliest lastAccessedTime to latest for (PassivationCheck passivationCheck : passivationChecks) { try { long timeNow = System.currentTimeMillis(); long timeIdle = timeNow - passivationCheck.getLastUpdate(); // if maxIdle time configured, means that we need to passivate sessions that have // exceeded the max allowed idle time if (passivationMax >= 0 && timeIdle > passivationMax) { passivationCheck.passivate(); } // If the session didn't exceed the passivationMaxIdleTime_, see // if the number of sessions managed by this manager greater than the max allowed // active sessions, passivate the session if it exceed passivationMinIdleTime_ else if (maxActiveAllowed_ > 0 && passivationMin > 0 && calcActiveSessions() >= maxActiveAllowed_ && timeIdle > passivationMin) { passivationCheck.passivate(); } else { // the entries are ordered by lastAccessed, so once // we don't passivate one, we won't passivate any break; } } catch (Exception e) { String unloadMark = passivationCheck.isUnloaded() ? "unloaded " : ""; log_.error("processExpirationPassivation(): failed passivating " + unloadMark + "session " + passivationCheck.getRealId(), e); } } } } catch (Exception ex) { log_.error("processExpirationPassivation(): failed with exception: " + ex, ex); } finally { SessionInvalidationTracker.resume(); } if (trace_) { log_.trace("processExpirationPassivation(): Completed ..."); log_.trace("processExpirationPassivation(): active sessions = " + calcActiveSessions()); log_.trace("processExpirationPassivation(): expired sessions = " + expiredCounter_); if (passivate) { log_.trace("processExpirationPassivation(): passivated count = " + getPassivatedSessionCount()); } } } /** * Session passivation logic for an actively managed session. * * @param realId the session id, minus any jvmRoute */ private void processSessionPassivation(String realId) { // get the session from the local map ClusteredSession<? extends OutgoingDistributableSessionData> session = findLocalSession(realId); // Remove actively managed session and add to the unloaded sessions // if it's already unloaded session (session == null) don't do anything, if (session != null) { synchronized (session) { if (trace_) { log_.trace("Passivating session with id: " + realId); } session.notifyWillPassivate(ClusteredSessionNotificationCause.PASSIVATION); getDistributedCacheManager().evictSession(realId); sessionPassivated(); // Put the session in the unloadedSessions map. This will // expose the session to regular invalidation. Object obj = unloadedSessions_.put(realId, new OwnedSessionUpdate(null, session.getLastAccessedTimeInternal(), session.getMaxInactiveInterval(), true)); if (trace_) { if (obj == null) { log_.trace("New session " + realId + " added to unloaded session map"); } else { log_.trace("Updated timestamp for unloaded session " + realId); } } sessions_.remove(realId); } } else if (trace_) { log_.trace("processSessionPassivation(): could not find session " + realId); } } /** * Session passivation logic for sessions only in the distributed store. * * @param realId the session id, minus any jvmRoute */ private void processUnloadedSessionPassivation(String realId, OwnedSessionUpdate osu) { if (trace_) { log_.trace("Passivating session with id: " + realId); } getDistributedCacheManager().evictSession(realId, osu.owner); osu.passivated = true; sessionPassivated(); } public void sessionActivated() { int pc = passivatedCount_.decrementAndGet(); // Correct for drift since we don't know the true passivation // count when we started. We can get activations of sessions // we didn't know were passivated. // FIXME -- is the above statement still correct? Is this needed? if (pc < 0) { // Just reverse our decrement. passivatedCount_.incrementAndGet(); } } private void sessionPassivated() { int pc = passivatedCount_.incrementAndGet(); int max = maxPassivatedCount_.get(); while (pc > max) { if (!maxPassivatedCount_ .compareAndSet(max, pc)) { max = maxPassivatedCount_.get(); } } } /** * Clear the underlying cache store. */ private void clearSessions() { boolean passivation = isPassivationEnabled(); // First, the sessions we have actively loaded ClusteredSession<? extends OutgoingDistributableSessionData>[] sessions = findLocalSessions(); for(int i=0; i < sessions.length; i++) { ClusteredSession<? extends OutgoingDistributableSessionData> ses = sessions[i]; if (trace_) { log_.trace("clearSessions(): clear session by expiring or passivating: " + ses); } try { // if session passivation is enabled, passivate sessions instead of expiring them which means // they'll be available to the manager for activation after a restart. if(passivation && ses.isValid()) { processSessionPassivation(ses.getRealId()); } else { boolean notify = true; //FIXME set to true when problem with cahce on stop is resolved boolean localCall = false; boolean localOnly = true; ses.expire(notify, localCall, localOnly, ClusteredSessionNotificationCause.UNDEPLOY); } } catch (Throwable t) { log_.warn("clearSessions(): Caught exception expiring or passivating session " + ses.getIdInternal(), t); } finally { // Guard against leaking memory if anything is holding a // ref to the session by clearing its internal state ses.recycle(); } } String action = passivation ? "evicting" : "removing"; Set<Map.Entry<String, OwnedSessionUpdate>> unloaded = unloadedSessions_.entrySet(); for (Iterator<Map.Entry<String, OwnedSessionUpdate>> it = unloaded.iterator(); it.hasNext();) { Map.Entry<String, OwnedSessionUpdate> entry = it.next(); String realId = entry.getKey(); try { if (passivation) { OwnedSessionUpdate osu = entry.getValue(); // Ignore the marker entries for our passivated sessions if (!osu.passivated) { //FIXME uncomment when problem with cahce on stop is resolved // getDistributedCacheManager().evictSession(realId, osu.owner); } } else { //FIXME uncomment when problem with cahce on stop is resolved // getDistributedCacheManager().removeSessionLocal(realId); } } catch (Exception e) { // Not as big a problem; we don't own the session log_.debug("Problem " + action + " session " + realId + " -- " + e); } it.remove(); } } /** * Callback from the distributed cache to notify us that a session * has been modified remotely. * * @param realId the session id, without any trailing jvmRoute * @param dataOwner the owner of the session. Can be <code>null</code> if * the owner is unknown. * @param distributedVersion the session's version per the distributed cache * @param timestamp the session's timestamp per the distributed cache * @param metadata the session's metadata per the distributed cache */ public boolean sessionChangedInDistributedCache(String realId, String dataOwner, int distributedVersion, long timestamp, DistributableSessionMetadata metadata) { boolean updated = true; ClusteredSession<? extends OutgoingDistributableSessionData> session = findLocalSession(realId); if (session != null) { // Need to invalidate the loaded session. We get back whether // this an actual version increment updated = session.setVersionFromDistributedCache(distributedVersion); if (updated && trace_) { log_.trace("session in-memory data is invalidated for id: " + realId + " new version: " + distributedVersion); } } else { int maxLife = metadata == null ? getMaxInactiveInterval() : metadata.getMaxInactiveInterval(); Object existing = unloadedSessions_.put(realId, new OwnedSessionUpdate(dataOwner, timestamp, maxLife, false)); if (existing == null) { calcActiveSessions(); if (trace_) { log_.trace("New session " + realId + " added to unloaded session map"); } } else if (trace_) { log_.trace("Updated timestamp for unloaded session " + realId); } } return updated; } @SuppressWarnings("unchecked") private static ContextClassLoaderSwitcher getContextClassLoaderSwitcher() { return (ContextClassLoaderSwitcher) AccessController.doPrivileged(ContextClassLoaderSwitcher.INSTANTIATOR); } /** * Removes the session from this Manager's collection of actively managed * sessions. Also removes the session from the distributed cache, both * on this server and on all other server to which this one replicates. */ public void remove(Session session) { ClusteredSession<? extends OutgoingDistributableSessionData> clusterSess = uncheckedCastSession(session); synchronized (clusterSess) { String realId = clusterSess.getRealId(); if (realId == null) return; if (trace_) { log_.trace("Removing session from store with id: " + realId); } try { clusterSess.removeMyself(); } finally { // We don't want to replicate this session at the end // of the request; the removal process took care of that ConvergedSessionReplicationContext.sessionExpired(clusterSess, realId, getSnapshotManager()); // Track this session to prevent reincarnation by this request // from the distributed cache SessionInvalidationTracker.sessionInvalidated(realId, this); sessions_.remove(realId); stats_.removeStats(realId); // Compute how long this session has been alive, and update // our statistics accordingly int timeAlive = (int) ((System.currentTimeMillis() - clusterSess.getCreationTimeInternal())/1000); sessionExpired(timeAlive); } } } /** * {@inheritDoc} * <p> * Removes the session from this Manager's collection of actively managed * sessions. Also removes the session from this server's copy of the * distributed cache (but does not remove it from other servers' * distributed cache). * </p> */ public void removeLocal(SipSession session) { ClusteredSipSession<? extends OutgoingDistributableSessionData> clusterSess = uncheckedCastSipSession(session); synchronized (clusterSess) { SipSessionKey key = clusterSess.getKey(); if (key == null) return; if (trace_) { log_.trace("Removing sip session from local store with id: " + key); } try { clusterSess.removeMyselfLocal(); } finally { // We don't want to replicate this session at the end // of the request; the removal process took care of that ConvergedSessionReplicationContext.sipSessionExpired(clusterSess, key, getSnapshotSipManager()); // Track this session to prevent reincarnation by this request // from the distributed cache ConvergedSessionInvalidationTracker.sipSessionInvalidated(key, this); sipManagerDelegate.removeSipSession(key); stats_.removeStats(key.toString()); // Compute how long this session has been alive, and update // our statistics accordingly // int timeAlive = (int) ((System.currentTimeMillis() - clusterSess.getCreationTimeInternal())/1000); // sipSessionExpired(timeAlive); } } } /** * {@inheritDoc} * <p> * Removes the session from this Manager's collection of actively managed * sessions. Also removes the session from this server's copy of the * distributed cache (but does not remove it from other servers' * distributed cache). * </p> */ public void removeLocal(SipApplicationSession session) { ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData> clusterSess = uncheckedCastSipApplicationSession(session); synchronized (clusterSess) { SipApplicationSessionKey key = clusterSess.getKey(); if (key == null) return; if (trace_) { log_.trace("Removing sip application session from local store with id: " + key); } try { clusterSess.removeMyselfLocal(); } finally { // We don't want to replicate this session at the end // of the request; the removal process took care of that ConvergedSessionReplicationContext.sipApplicationSessionExpired(clusterSess, key, getSnapshotSipManager()); // Track this session to prevent reincarnation by this request // from the distributed cache ConvergedSessionInvalidationTracker.sipApplicationSessionInvalidated(key, this); sipManagerDelegate.removeSipApplicationSession(key); stats_.removeStats(key.toString()); // Compute how long this session has been alive, and update // our statistics accordingly // int timeAlive = (int) ((System.currentTimeMillis() - clusterSess.getCreationTimeInternal())/1000); // sipApplicationSessionExpired(timeAlive); } } } public boolean storeSipSession(SipSession baseSession) { boolean stored = false; if (baseSession != null && started_) { ClusteredSipSession<? extends OutgoingDistributableSessionData> session = uncheckedCastSipSession(baseSession); synchronized (session) { if (logger.isDebugEnabled()) { logger.debug("check to see if needs to store and replicate " + "session with id " + session.getId()); } if (session.isValid() && (session.isSessionDirty() || session .getMustReplicateTimestamp())) { if(logger.isInfoEnabled()) { logger.info("replicating following sip session " + session.getId()); } String realId = session.getId(); // Notify all session attributes that they get serialized // (SRV 7.7.2) long begin = System.currentTimeMillis(); session.notifyWillPassivate(ClusteredSessionNotificationCause.REPLICATION); long elapsed = System.currentTimeMillis() - begin; stats_.updatePassivationStats(realId, elapsed); // Do the actual replication begin = System.currentTimeMillis(); processSipSessionRepl(session); elapsed = System.currentTimeMillis() - begin; stored = true; stats_.updateReplicationStats(realId, elapsed); } } } return stored; } public boolean storeSipApplicationSession(SipApplicationSession baseSession) { boolean stored = false; if (baseSession != null && started_) { ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData> session = uncheckedCastSipApplicationSession(baseSession); synchronized (session) { if (logger.isDebugEnabled()) { log_.debug("check to see if needs to store and replicate " + "session with id " + session.getId()); } if (session.isValid() && (session.isSessionDirty() || session .getMustReplicateTimestamp())) { if(logger.isInfoEnabled()) { logger.info("replicating following sip application session " + session.getId()); } String realId = session.getId(); // Notify all session attributes that they get serialized // (SRV 7.7.2) long begin = System.currentTimeMillis(); session.notifyWillPassivate(ClusteredSessionNotificationCause.REPLICATION); long elapsed = System.currentTimeMillis() - begin; stats_.updatePassivationStats(realId, elapsed); // Do the actual replication begin = System.currentTimeMillis(); processSipApplicationSessionRepl(session); // we make sure we replicate all underlying sip sessions that could have been made dirty Iterator<ClusteredSipSession<OutgoingDistributableSessionData>> sipSessionIt = (Iterator<ClusteredSipSession<OutgoingDistributableSessionData>>) ((MobicentsSipApplicationSession)session).getSessions("SIP"); if(logger.isDebugEnabled()) { logger.debug("checking if the underlying sip sessions are dirty and need to be replicated as well"); } while (sipSessionIt.hasNext()) { ClusteredSipSession sipSession = (ClusteredSipSession) sipSessionIt .next(); if(logger.isDebugEnabled()) { logger.debug("checking if the underlying sip session " + sipSession.getKey() + " is dirty and need to be replicated as well"); } storeSipSession(sipSession); } elapsed = System.currentTimeMillis() - begin; stored = true; stats_.updateReplicationStats(realId, elapsed); } } } return stored; } public void add(SipSession session) { if (session == null) return; if (!(session instanceof ClusteredSipSession)) { throw new IllegalArgumentException( "You can only add instances of " + "type ClusteredSession to this Manager. Session class name: " + session.getClass().getName()); } // add((ClusteredSession) session, true); add(uncheckedCastSipSession(session), false); } public void add(SipApplicationSession session) { if (session == null) return; if (!(session instanceof ClusteredSipApplicationSession)) { throw new IllegalArgumentException( "You can only add instances of " + "type ClusteredSession to this Manager. Session class name: " + session.getClass().getName()); } // add((ClusteredSession) session, true); add(uncheckedCastSipApplicationSession(session), false); } /** * Adds the given session to the collection of those being managed by this * Manager. * * @param session the session. Cannot be <code>null</code>. * @param replicate whether the session should be replicated * * @throws NullPointerException if <code>session</code> is <code>null</code>. */ private void add(ClusteredSipSession<? extends OutgoingDistributableSessionData> session, boolean replicate) { // TODO -- why are we doing this check? The request checks session // validity and will expire the session; this seems redundant if (!session.isValid()) { // Not an error; this can happen if a failover request pulls in an // outdated session from the distributed cache (see TODO above) log_.debug("Cannot add session with id=" + session.getKey() + " because it is invalid"); return; } String realId = session.getRealId(); SipSessionKey key = session.getKey(); Object existing = ((ClusteredSipManagerDelegate)sipManagerDelegate).putSipSession(key, session); unloadedSipSessions_.remove(key); if (!session.equals(existing)) { if (replicate) { storeSipSession(session); } // Update counters // calcActiveSipSessions(); if (trace_) { log_.trace("Session with id=" + session.getKey() + " added. " + "Current active sessions " + localActiveCounter_.get()); } } } /** * Adds the given session to the collection of those being managed by this * Manager. * * @param session the session. Cannot be <code>null</code>. * @param replicate whether the session should be replicated * * @throws NullPointerException if <code>session</code> is <code>null</code>. */ private void add(ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData> session, boolean replicate) { // TODO -- why are we doing this check? The request checks session // validity and will expire the session; this seems redundant if (!session.isValid()) { // Not an error; this can happen if a failover request pulls in an // outdated session from the distributed cache (see TODO above) log_.debug("Cannot add session with id=" + session.getKey() + " because it is invalid"); return; } String realId = session.getRealId(); SipApplicationSessionKey key = session.getKey(); Object existing = ((ClusteredSipManagerDelegate)sipManagerDelegate).putSipApplicationSession(key, session); unloadedSipSessions_.remove(key); if (!session.equals(existing)) { if (replicate) { storeSipApplicationSession(session); } // Update counters // calcActiveSipApplicationSessions(); if (trace_) { log_.trace("Session with id=" + session.getKey() + " added. " + "Current active sessions " + localActiveCounter_.get()); } } } /** * Returns all the sessions that are being actively managed by this manager. * This includes those that were created on this server, those that were * brought into local management by a call to * {@link #findLocalSession(String)} as well as all sessions brought into * local management by a call to {@link #findSessions()}. */ protected ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData>[] findLocalSipApplicationSessions() { Iterator<ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData>> it = (Iterator) sipManagerDelegate.getAllSipApplicationSessions(); List<ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData>> coll = new ArrayList(); while (it.hasNext()) { ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData> clusteredSipApplicationSession = it.next(); coll.add(clusteredSipApplicationSession); } @SuppressWarnings("unchecked") ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData>[] sess = new ClusteredSipApplicationSession[coll .size()]; return coll.toArray(sess); } /** * Returns all the sessions that are being actively managed by this manager. * This includes those that were created on this server, those that were * brought into local management by a call to * {@link #findLocalSession(String)} as well as all sessions brought into * local management by a call to {@link #findSessions()}. */ protected ClusteredSipSession<? extends OutgoingDistributableSessionData>[] findLocalSipSessions() { Iterator<ClusteredSipSession<? extends OutgoingDistributableSessionData>> it = (Iterator) sipManagerDelegate.getAllSipSessions(); List<ClusteredSipSession<? extends OutgoingDistributableSessionData>> coll = new ArrayList(); while (it.hasNext()) { ClusteredSipSession<? extends OutgoingDistributableSessionData> clusteredSipSession = it.next(); coll.add(clusteredSipSession); } @SuppressWarnings("unchecked") ClusteredSipSession<? extends OutgoingDistributableSessionData>[] sess = new ClusteredSipSession[coll .size()]; return coll.toArray(sess); } /** * Returns the given sip session if it is being actively managed by this * manager. An actively managed sip session is on that was either created on * this server, brought into local management by a call to * {@link #findLocalSipSession(String)} or brought into local management by a * call to {@link #findSipSessions()}. * * @param key * the session key, with any trailing jvmRoute removed. */ public ClusteredSipSession findLocalSipSession(final SipSessionKey key, final boolean create, final MobicentsSipApplicationSession sipApplicationSessionImpl) { return (ClusteredSipSession) sipManagerDelegate.getSipSession(key, create, sipManagerDelegate.getSipFactoryImpl(), sipApplicationSessionImpl); } /** * Returns the given sip application session if it is being actively managed by this * manager. An actively managed sip application session is on that was either created on * this server, brought into local management by a call to * {@link #findLocalSipApplicationSession(String)} or brought into local management by a * call to {@link #findSipApplicationSessions()}. * * @param key * the session key, with any trailing jvmRoute removed. */ public ClusteredSipApplicationSession findLocalSipApplicationSession(final SipApplicationSessionKey key, final boolean create) { return (ClusteredSipApplicationSession) sipManagerDelegate.getSipApplicationSession(key, create); } /** * {@inheritDoc} */ protected void processExpirationSipSessionPassivation() { boolean expire = maxInactiveInterval_ >= 0; boolean passivate = isPassivationEnabled(); long passivationMax = passivationMaxIdleTime_ * 1000L; long passivationMin = passivationMinIdleTime_ * 1000L; if (trace_) { // log_.trace("processExpirationPassivation(): Looking for sessions that have expired ..."); // log_.trace("processExpirationPassivation(): active sessions = " + calcActiveSipSessions()); // log_.trace("processExpirationPassivation(): expired sessions = " + expiredCounter_); // if (passivate) // { // log_.trace("processExpirationPassivation(): passivated count = " + getPassivatedSipSessionCount()); // } } // Holder for sessions or OwnedSessionUpdates that survive expiration, // sorted by last acccessed time Set<SipSessionPassivationCheck> passivationChecks = new TreeSet<SipSessionPassivationCheck>(); try { // Don't track sessions invalidated via this method as if they // were going to be re-requested by the thread ConvergedSessionInvalidationTracker.suspend(); // First, handle the sessions we are actively managing ClusteredSipSession<? extends OutgoingDistributableSessionData> sessions[] = findLocalSipSessions(); for (int i = 0; i < sessions.length; ++i) { try { ClusteredSipSession<? extends OutgoingDistributableSessionData> session = sessions[i]; if(session == null) { log_.warn("processExpirationPassivation(): processing null session at index " +i); continue; } if (expire) { // JBAS-2403. Check for outdated sessions where we think // the local copy has timed out. If found, refresh the // session from the cache in case that might change the timeout if (session.isOutdated() && !(session.isValid())) { // FIXME in AS 5 every time we get a notification from the distributed // cache of an update, we get the latest timestamp. So // we shouldn't need to do a full session load here. A load // adds a risk of an unintended data gravitation. // JBAS-2792 don't assign the result of loadSession to session // just update the object from the cache or fall through if // the session has been removed from the cache loadSipSession(session.getKey(), false, getSipFactoryImpl(), session.getSipApplicationSession()); } // Do a normal invalidation check that will expire the // session if it has timed out // DON'T SYNCHRONIZE on session here -- isValid() and // expire() are meant to be multi-threaded and synchronize // properly internally; synchronizing externally can lead // to deadlocks!! if (!session.isValid()) continue; } // we now have a valid session; store it so we can check later // if we need to passivate it if (passivate) { passivationChecks.add(new SipSessionPassivationCheck(session)); } } catch (Exception ex) { log_.error("processExpirationPassivation(): failed handling " + sessions[i].getKey() + " with exception: " + ex, ex); } } // Next, handle any unloaded sessions // We may have not gotten replication of a timestamp for requests // that occurred w/in maxUnreplicatedInterval_ of the previous // request. So we add a grace period to avoid flushing a session early // and permanently losing part of its node structure in JBoss Cache. long maxUnrep = getMaxUnreplicatedInterval() < 0 ? 60 : getMaxUnreplicatedInterval(); Map<ClusteredSipSessionKey, OwnedSessionUpdate> unloaded = new HashMap<ClusteredSipSessionKey, OwnedSessionUpdate>(unloadedSipSessions_); for (Map.Entry<ClusteredSipSessionKey, OwnedSessionUpdate> entry : unloaded.entrySet()) { ClusteredSipSessionKey key = entry.getKey(); OwnedSessionUpdate osu = entry.getValue(); long now = System.currentTimeMillis(); long elapsed = (now - osu.updateTime); try { if (expire && osu.maxInactive >= 1 && elapsed >= (osu.maxInactive + maxUnrep) * 1000L) { //if (osu.passivated && osu.owner == null) if (osu.passivated) { // Passivated session needs to be expired. A call to // findSession will bring it out of passivation SipSession session = getSipSession(key.getSipSessionKey(), false, getSipFactoryImpl(), getSipApplicationSession(key.getSipApplicationSessionKey(), false)); if (session != null) { session.isValid(); // will expire continue; } } // If we get here either !osu.passivated, or we don't own // the session or the session couldn't be reactivated (invalidated by user). // Either way, do a cleanup getDistributedCacheConvergedSipManager().removeSessionLocal(key.getSipApplicationSessionKey(), key.getSipSessionKey(), osu.owner); unloadedSipSessions_.remove(key); stats_.removeStats(key.toString()); } else if (passivate && !osu.passivated) { // we now have a valid session; store it so we can check later // if we need to passivate it passivationChecks.add(new SipSessionPassivationCheck(key, osu)); } } catch (Exception ex) { log_.error("processExpirationPassivation(): failed handling unloaded session " + key, ex); } } // Now, passivations if (passivate) { // Iterate through sessions, earliest lastAccessedTime to latest for (SipSessionPassivationCheck passivationCheck : passivationChecks) { try { long timeNow = System.currentTimeMillis(); long timeIdle = timeNow - passivationCheck.getLastUpdate(); // if maxIdle time configured, means that we need to passivate sessions that have // exceeded the max allowed idle time if (passivationMax >= 0 && timeIdle > passivationMax) { passivationCheck.passivate(); } // If the session didn't exceed the passivationMaxIdleTime_, see // if the number of sessions managed by this manager greater than the max allowed // active sessions, passivate the session if it exceed passivationMinIdleTime_ else if (maxActiveAllowed_ > 0 && passivationMin > 0 && calcActiveSessions() >= maxActiveAllowed_ && timeIdle > passivationMin) { passivationCheck.passivate(); } else { // the entries are ordered by lastAccessed, so once // we don't passivate one, we won't passivate any break; } } catch (Exception e) { String unloadMark = passivationCheck.isUnloaded() ? "unloaded " : ""; log_.error("processExpirationPassivation(): failed passivating " + unloadMark + "session " + passivationCheck.getKey(), e); } } } } catch (Exception ex) { log_.error("processExpirationPassivation(): failed with exception: " + ex, ex); } finally { SessionInvalidationTracker.resume(); } // if (trace_) // { // log_.trace("processExpirationPassivation(): Completed ..."); // log_.trace("processExpirationPassivation(): active sessions = " + calcActiveSipSessions()); // log_.trace("processExpirationPassivation(): expired sessions = " + expiredCounter_); // if (passivate) // { // log_.trace("processExpirationPassivation(): passivated count = " + getPassivatedSipSessionCount()); // } // } } /** * {@inheritDoc} */ protected void processExpirationSipApplicationSessionPassivation() { boolean expire = maxInactiveInterval_ >= 0; boolean passivate = isPassivationEnabled(); long passivationMax = passivationMaxIdleTime_ * 1000L; long passivationMin = passivationMinIdleTime_ * 1000L; // if (trace_) // { // log_.trace("processExpirationPassivation(): Looking for sessions that have expired ..."); // log_.trace("processExpirationPassivation(): active sessions = " + calcActiveSipApplicationSessions()); // log_.trace("processExpirationPassivation(): expired sessions = " + expiredCounter_); // if (passivate) // { // log_.trace("processExpirationPassivation(): passivated count = " + getPassivatedSipApplicationSessionCount()); // } // } // Holder for sessions or OwnedSessionUpdates that survive expiration, // sorted by last acccessed time Set<SipApplicationSessionPassivationCheck> passivationChecks = new TreeSet<SipApplicationSessionPassivationCheck>(); try { // Don't track sessions invalidated via this method as if they // were going to be re-requested by the thread ConvergedSessionInvalidationTracker.suspend(); // First, handle the sessions we are actively managing ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData> sessions[] = findLocalSipApplicationSessions(); for (int i = 0; i < sessions.length; ++i) { try { ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData> session = sessions[i]; if(session == null) { log_.warn("processExpirationPassivation(): processing null session at index " +i); continue; } if (expire) { // JBAS-2403. Check for outdated sessions where we think // the local copy has timed out. If found, refresh the // session from the cache in case that might change the timeout if (session.isOutdated() && !(session.isValid())) { // FIXME in AS 5 every time we get a notification from the distributed // cache of an update, we get the latest timestamp. So // we shouldn't need to do a full session load here. A load // adds a risk of an unintended data gravitation. // JBAS-2792 don't assign the result of loadSession to session // just update the object from the cache or fall through if // the session has been removed from the cache loadSipApplicationSession(session.getKey(), false); } // Do a normal invalidation check that will expire the // session if it has timed out // DON'T SYNCHRONIZE on session here -- isValid() and // expire() are meant to be multi-threaded and synchronize // properly internally; synchronizing externally can lead // to deadlocks!! if (!session.isValid()) continue; } // we now have a valid session; store it so we can check later // if we need to passivate it if (passivate) { passivationChecks.add(new SipApplicationSessionPassivationCheck(session)); } } catch (Exception ex) { log_.error("processExpirationPassivation(): failed handling " + sessions[i].getKey() + " with exception: " + ex, ex); } } // Next, handle any unloaded sessions // We may have not gotten replication of a timestamp for requests // that occurred w/in maxUnreplicatedInterval_ of the previous // request. So we add a grace period to avoid flushing a session early // and permanently losing part of its node structure in JBoss Cache. long maxUnrep = getMaxUnreplicatedInterval() < 0 ? 60 : getMaxUnreplicatedInterval(); Map<SipApplicationSessionKey, OwnedSessionUpdate> unloaded = new HashMap<SipApplicationSessionKey, OwnedSessionUpdate>(unloadedSipApplicationSessions_); for (Map.Entry<SipApplicationSessionKey, OwnedSessionUpdate> entry : unloaded.entrySet()) { SipApplicationSessionKey key = entry.getKey(); OwnedSessionUpdate osu = entry.getValue(); long now = System.currentTimeMillis(); long elapsed = (now - osu.updateTime); try { if (expire && osu.maxInactive >= 1 && elapsed >= (osu.maxInactive + maxUnrep) * 1000L) { //if (osu.passivated && osu.owner == null) if (osu.passivated) { // Passivated session needs to be expired. A call to // findSession will bring it out of passivation SipApplicationSession session = getSipApplicationSession(key, false); if (session != null) { session.isValid(); // will expire continue; } } // If we get here either !osu.passivated, or we don't own // the session or the session couldn't be reactivated (invalidated by user). // Either way, do a cleanup getDistributedCacheConvergedSipManager().removeSessionLocal(key, osu.owner); unloadedSipSessions_.remove(key); stats_.removeStats(key.toString()); } else if (passivate && !osu.passivated) { // we now have a valid session; store it so we can check later // if we need to passivate it passivationChecks.add(new SipApplicationSessionPassivationCheck(key, osu)); } } catch (Exception ex) { log_.error("processExpirationPassivation(): failed handling unloaded session " + key, ex); } } // Now, passivations if (passivate) { // Iterate through sessions, earliest lastAccessedTime to latest for (SipApplicationSessionPassivationCheck passivationCheck : passivationChecks) { try { long timeNow = System.currentTimeMillis(); long timeIdle = timeNow - passivationCheck.getLastUpdate(); // if maxIdle time configured, means that we need to passivate sessions that have // exceeded the max allowed idle time if (passivationMax >= 0 && timeIdle > passivationMax) { passivationCheck.passivate(); } // If the session didn't exceed the passivationMaxIdleTime_, see // if the number of sessions managed by this manager greater than the max allowed // active sessions, passivate the session if it exceed passivationMinIdleTime_ else if (maxActiveAllowed_ > 0 && passivationMin > 0 && calcActiveSessions() >= maxActiveAllowed_ && timeIdle > passivationMin) { passivationCheck.passivate(); } else { // the entries are ordered by lastAccessed, so once // we don't passivate one, we won't passivate any break; } } catch (Exception e) { String unloadMark = passivationCheck.isUnloaded() ? "unloaded " : ""; log_.error("processExpirationPassivation(): failed passivating " + unloadMark + "session " + passivationCheck.getKey(), e); } } } } catch (Exception ex) { log_.error("processExpirationPassivation(): failed with exception: " + ex, ex); } finally { SessionInvalidationTracker.resume(); } // if (trace_) // { // log_.trace("processExpirationPassivation(): Completed ..."); // log_.trace("processExpirationPassivation(): active sessions = " + calcActiveSipApplicationSessions()); // log_.trace("processExpirationPassivation(): expired sessions = " + expiredCounter_); // if (passivate) // { // log_.trace("processExpirationPassivation(): passivated count = " + getPassivatedSipApplicationSessionCount()); // } // } } /** * Loads a session from the distributed store. If an existing session with * the id is already under local management, that session's internal state * will be updated from the distributed store. Otherwise a new session will * be created and added to the collection of those sessions under local * management. * * @param key * id of the session-id with any jvmRoute removed * * @return the session or <code>null</code> if the session cannot be found * in the distributed store * * TODO refactor this into 2 overloaded methods -- one that takes a * ClusteredSession and populates it and one that takes an id, * creates the session and calls the first */ protected ClusteredSipSession loadSipSession(final SipSessionKey key, final boolean create, final SipFactoryImpl sipFactoryImpl, final MobicentsSipApplicationSession sipApplicationSessionImpl) { if (key == null) { return null; } if(logger.isDebugEnabled()) { logger.debug("load sip session " + key + ", create = " + create + " sip app session = "+ sipApplicationSessionImpl); } SipFactoryImpl sipFactory = sipFactoryImpl; if(sipFactory == null) { sipFactory = this.getSipFactoryImpl(); } long begin = System.currentTimeMillis(); boolean mustAdd = false; boolean passivated = false; ClusteredSipSession<? extends OutgoingDistributableSessionData> session = (ClusteredSipSession) sipManagerDelegate.getSipSession(key, create, sipFactory, sipApplicationSessionImpl); ClusteredSipSession<? extends OutgoingDistributableSessionData> newTempSession = session; boolean initialLoad = false; if (session == null && sipApplicationSessionImpl != null && create) { // This is either the first time we've seen this session on this // server, or we previously expired it and have since gotten // a replication message from another server mustAdd = true; initialLoad = true; newTempSession = (ClusteredSipSession<? extends OutgoingDistributableSessionData>) sipManagerDelegate.getSipSession(key, true, sipFactory, sipApplicationSessionImpl); session = newTempSession; OwnedSessionUpdate osu = unloadedSipSessions_.get(key); passivated = (osu != null && osu.passivated); } if(session != null) { synchronized (session) { boolean doTx = false; BatchingManager batchingManager = getDistributedCacheConvergedSipManager().getBatchingManager(); try { // We need transaction so any data gravitation replication // is sent in batch. // Don't do anything if there is already transaction context // associated with this thread. if (batchingManager.isBatchInProgress() == false) { batchingManager.startBatch(); doTx = true; } IncomingDistributableSessionData data = getDistributedCacheConvergedSipManager().getSessionData(sipApplicationSessionImpl.getKey(), key, initialLoad); if (data != null) { session.update(data); } else if(mustAdd) { // Clunky; we set the session variable to null to indicate // no data so move on session = null; } if (session != null) { ClusteredSessionNotificationCause cause = passivated ? ClusteredSessionNotificationCause.ACTIVATION : ClusteredSessionNotificationCause.FAILOVER; session.notifyDidActivate(cause); } // sessionInCache = proxy_.loadSipSession(sipApplicationSessionImpl.getId(), key.toString(), newTempSession); } catch (Exception ex) { try { // if(doTx) // Let's set it no matter what. batchingManager.setBatchRollbackOnly(); } catch (Exception exn) { log_.error("Problem rolling back session mgmt transaction", exn); } // We will need to alert Tomcat of this exception. if (ex instanceof RuntimeException) throw (RuntimeException) ex; throw new RuntimeException("Failed to load session " + key.toString(), ex); } finally { if (doTx) batchingManager.endBatch(); } } } if (session != null) { if (mustAdd) { add(session, false); // don't replicate if (!passivated) { session.tellNew(ClusteredSessionNotificationCause.FAILOVER); } } long elapsed = System.currentTimeMillis() - begin; stats_.updateLoadStats(key.toString(), elapsed); if (trace_) { log_ .trace("loadSession(): id= " + key + ", session=" + session); } } else if (trace_) { log_.trace("loadSession(): session " + key + " not found in distributed cache"); } // if (sessionInCache != null) { // // Need to initialize. // sessionInCache.initAfterLoad(this); // if (mustAdd) // add(sessionInCache, false); // don't replicate // long elapsed = System.currentTimeMillis() - begin; // stats_.updateLoadStats(key.toString(), elapsed); // // if (log_.isDebugEnabled()) { // log_.debug("loadSession(): id= " + key.toString() + ", session=" // + newTempSession); // } // return sessionInCache; // } else if (log_.isDebugEnabled()) { // log_.debug("loadSession(): session " + key.toString() // + " not found in distributed cache"); // } return session; } /** * Loads a session from the distributed store. If an existing session with * the id is already under local management, that session's internal state * will be updated from the distributed store. Otherwise a new session will * be created and added to the collection of those sessions under local * management. * * @param key * id of the session-id with any jvmRoute removed * * @return the session or <code>null</code> if the session cannot be found * in the distributed store * * TODO refactor this into 2 overloaded methods -- one that takes a * ClusteredSession and populates it and one that takes an id, * creates the session and calls the first */ protected ClusteredSipApplicationSession loadSipApplicationSession(final SipApplicationSessionKey key, final boolean create) { if (key == null) { return null; } long begin = System.currentTimeMillis(); boolean mustAdd = false; boolean passivated = false; boolean initialLoad = false; ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData> session = (ClusteredSipApplicationSession) sipManagerDelegate.getSipApplicationSession(key, create); if (session == null) { // This is either the first time we've seen this session on this // server, or we previously expired it and have since gotten // a replication message from another server mustAdd = true; initialLoad = true; session = (ClusteredSipApplicationSession) sipManagerDelegate.getSipApplicationSession(key, true); OwnedSessionUpdate osu = unloadedSipApplicationSessions_.get(key); passivated = (osu != null && osu.passivated); } synchronized (session) { // ClusteredSipApplicationSession sessionInCache = null; boolean doTx = false; BatchingManager batchingManager = getDistributedCacheConvergedSipManager().getBatchingManager(); try { // We need transaction so any data gravitation replication // is sent in batch. // Don't do anything if there is already transaction context // associated with this thread. if (batchingManager.isBatchInProgress() == false) { batchingManager.startBatch(); doTx = true; } IncomingDistributableSessionData data = getDistributedCacheConvergedSipManager().getSessionData(key, initialLoad); if (data != null) { session.update(data); } else if(mustAdd) { // Clunky; we set the session variable to null to indicate // no data so move on session = null; } if (session != null) { ClusteredSessionNotificationCause cause = passivated ? ClusteredSessionNotificationCause.ACTIVATION : ClusteredSessionNotificationCause.FAILOVER; session.notifyDidActivate(cause); } } catch (Exception ex) { try { // if(doTx) // Let's set it no matter what. batchingManager.setBatchRollbackOnly(); } catch (Exception exn) { log_.error("Problem rolling back session mgmt transaction", exn); } // We will need to alert Tomcat of this exception. if (ex instanceof RuntimeException) throw (RuntimeException) ex; throw new RuntimeException("Failed to load session " + key, ex); } finally { if (doTx) batchingManager.endBatch(); } if (session != null) { if (mustAdd) { add(session, false); // don't replicate if (!passivated) { session.tellNew(ClusteredSessionNotificationCause.FAILOVER); } } long elapsed = System.currentTimeMillis() - begin; stats_.updateLoadStats(key.toString(), elapsed); if (trace_) { log_.trace("loadSession(): id= " + key.toString() + ", session=" + session); } } else if (trace_) { log_.trace("loadSession(): session " + key.toString() + " not found in distributed cache"); } // if (sessionInCache != null) { // // Need to initialize. // sessionInCache.initAfterLoad(this); // if (mustAdd) // add(sessionInCache, false); // don't replicate // long elapsed = System.currentTimeMillis() - begin; // stats_.updateLoadStats(key.toString(), elapsed); // // if (log_.isDebugEnabled()) { // log_.debug("loadSession(): id= " + key.toString() + ", session=" // + sessionInCache); // } // return sessionInCache; // } else if (log_.isDebugEnabled()) { // log_.debug("loadSession(): session " + key.toString() // + " not found in distributed cache"); // } } // ConvergedSessionReplicationContext.bindSipApplicationSession(session, // snapshotManager_); return session; } /** * Places the current session contents in the distributed cache and * replicates them to the cluster * * @param session * the session. Cannot be <code>null</code>. */ protected void processSipSessionRepl(ClusteredSipSession session) { // If we are using SESSION granularity, we don't want to initiate a TX // for a single put boolean notSession = (getReplicationGranularity() != ReplicationGranularity.SESSION); boolean doTx = false; BatchingManager batchingManager = getDistributedCacheConvergedSipManager().getBatchingManager(); try { // We need transaction so all the replication are sent in batch. // Don't do anything if there is already transaction context // associated with this thread. if(notSession && batchingManager.isBatchInProgress() == false) { batchingManager.startBatch(); doTx = true; } session.processSipSessionReplication(); } catch (Exception ex) { if (log_.isDebugEnabled()) log_.debug("processSessionRepl(): failed with exception", ex); try { // if(doTx) // Let's setRollbackOnly no matter what. // (except if there's no tx due to SESSION (JBAS-3840)) if (notSession) batchingManager.setBatchRollbackOnly(); } catch (Exception exn) { log_.error("Caught exception rolling back transaction", exn); } // We will need to alert Tomcat of this exception. if (ex instanceof RuntimeException) throw (RuntimeException) ex; throw new RuntimeException( "JBossCacheManager.processSessionRepl(): " + "failed to replicate session.", ex); } finally { if (doTx) batchingManager.endBatch(); } } /** * Places the current session contents in the distributed cache and * replicates them to the cluster * * @param session * the session. Cannot be <code>null</code>. */ protected void processSipApplicationSessionRepl(ClusteredSipApplicationSession session) { // If we are using SESSION granularity, we don't want to initiate a TX // for a single put boolean notSession = (getReplicationGranularity() != ReplicationGranularity.SESSION); boolean doTx = false; BatchingManager batchingManager = getDistributedCacheConvergedSipManager().getBatchingManager(); try { // We need transaction so all the replication are sent in batch. // Don't do anything if there is already transaction context // associated with this thread. if(notSession && batchingManager.isBatchInProgress() == false) { batchingManager.startBatch(); doTx = true; } session.processSipApplicationSessionReplication(); } catch (Exception ex) { if (log_.isDebugEnabled()) log_.debug("processSessionRepl(): failed with exception", ex); try { // if(doTx) // Let's setRollbackOnly no matter what. // (except if there's no tx due to SESSION (JBAS-3840)) if (notSession) batchingManager.setBatchRollbackOnly(); } catch (Exception exn) { log_.error("Caught exception rolling back transaction", exn); } // We will need to alert Tomcat of this exception. if (ex instanceof RuntimeException) throw (RuntimeException) ex; throw new RuntimeException( "JBossCacheManager.processSessionRepl(): " + "failed to replicate session.", ex); } finally { if (doTx) batchingManager.endBatch(); } } /** * Session passivation logic for an actively managed session. * * @param realId * the session id, minus any jvmRoute */ private void processSipSessionPassivation(SipSessionKey key) { // get the session from the local map ClusteredSipSession<? extends OutgoingDistributableSessionData> session = findLocalSipSession(key, false, null); // Remove actively managed session and add to the unloaded sessions // if it's already unloaded session (session == null) don't do anything, if (session != null) { synchronized (session) { if (trace_) { log_.trace("Passivating session with id: " + key); } session .notifyWillPassivate(ClusteredSessionNotificationCause.PASSIVATION); getDistributedCacheConvergedSipManager().evictSession(session.getSipApplicationSession().getKey(), key); sipSessionPassivated(); // Put the session in the unloadedSessions map. This will // expose the session to regular invalidation. Object obj = unloadedSipSessions_.put(new ClusteredSipSessionKey(key, session.getSipApplicationSession().getKey()), new OwnedSessionUpdate(null, session .getLastAccessedTime(), session .getMaxInactiveInterval(), true)); if (trace_) { if (obj == null) { log_.trace("New session " + key + " added to unloaded session map"); } else { log_.trace("Updated timestamp for unloaded session " + key); } } sessions_.remove(key); } } else if (trace_) { log_.trace("processSessionPassivation(): could not find session " + key); } } /** * Session passivation logic for an actively managed session. * * @param realId * the session id, minus any jvmRoute */ private void processSipApplicationSessionPassivation(SipApplicationSessionKey key) { // get the session from the local map ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData> session = findLocalSipApplicationSession(key, false); // Remove actively managed session and add to the unloaded sessions // if it's already unloaded session (session == null) don't do anything, if (session != null) { synchronized (session) { if (trace_) { log_.trace("Passivating session with id: " + key); } session .notifyWillPassivate(ClusteredSessionNotificationCause.PASSIVATION); getDistributedCacheConvergedSipManager().evictSession(key); sipApplicationSessionPassivated(); // Put the session in the unloadedSessions map. This will // expose the session to regular invalidation. Object obj = unloadedSipApplicationSessions_.put(key, new OwnedSessionUpdate(null, session .getLastAccessedTime(), session .getMaxInactiveInterval(), true)); if (trace_) { if (obj == null) { log_.trace("New session " + key + " added to unloaded session map"); } else { log_.trace("Updated timestamp for unloaded session " + key); } } sessions_.remove(key); } } else if (trace_) { log_.trace("processSessionPassivation(): could not find session " + key); } } /** * Session passivation logic for sessions only in the distributed store. * * @param realId * the session id, minus any jvmRoute */ private void processUnloadedSipSessionPassivation(ClusteredSipSessionKey key, OwnedSessionUpdate osu) { if (trace_) { log_.trace("Passivating session with id: " + key); } getDistributedCacheConvergedSipManager().evictSession(key.getSipApplicationSessionKey(), key.getSipSessionKey(), osu.owner); osu.passivated = true; sipSessionPassivated(); } /** * Session passivation logic for sessions only in the distributed store. * * @param realId * the session id, minus any jvmRoute */ private void processUnloadedSipApplicationSessionPassivation(SipApplicationSessionKey key, OwnedSessionUpdate osu) { if (trace_) { log_.trace("Passivating session with id: " + key); } getDistributedCacheConvergedSipManager().evictSession(key, osu.owner); osu.passivated = true; sipApplicationSessionPassivated(); } private void sipSessionPassivated() { int pc = sipSessionPassivatedCount_.incrementAndGet(); int max = sipSessionMaxPassivatedCount_.get(); while (pc > max) { if (!sipSessionMaxPassivatedCount_.compareAndSet(max, pc)) { max = sipSessionMaxPassivatedCount_.get(); } } } private void sipApplicationSessionPassivated() { int pc = sipApplicationSessionPassivatedCount_.incrementAndGet(); int max = sipApplicationSessionMaxPassivatedCount_.get(); while (pc > max) { if (!sipApplicationSessionMaxPassivatedCount_.compareAndSet(max, pc)) { max = sipApplicationSessionMaxPassivatedCount_.get(); } } } // /** // * Gets the ids of all sessions in the distributed cache and adds them to // * the unloaded sessions map, along with their lastAccessedTime and their // * maxInactiveInterval. Passivates overage or excess sessions. // */ // private void initializeUnloadedSipSessions() { // Map<SipSessionKey, String> sessions = getDistributedCacheConvergedSipManager().getSipSessionKeys(); // if (sessions != null) { // boolean passivate = isPassivationEnabled(); // // long passivationMax = passivationMaxIdleTime_ * 1000L; // long passivationMin = passivationMinIdleTime_ * 1000L; // // for (Map.Entry<SipSessionKey, String> entry : sessions.entrySet()) { // SipSessionKey key = entry.getKey(); // String owner = entry.getValue(); // // long ts = -1; // DistributableSessionMetadata md = null; // try { // IncomingDistributableSessionData sessionData = getDistributedCacheConvergedSipManager() // .getSessionData(key, owner, false); // ts = sessionData.getTimestamp(); // md = sessionData.getMetadata(); // } catch (Exception e) { // // most likely a lock conflict if the session is being // // updated remotely; // // ignore it and use default values for timstamp and // // maxInactive // log_.debug("Problem reading metadata for session " + key // + " -- " + e.toString()); // } // // long lastMod = ts == -1 ? System.currentTimeMillis() : ts; // int maxLife = md == null ? getMaxInactiveInterval() : md // .getMaxInactiveInterval(); // // OwnedSessionUpdate osu = new OwnedSessionUpdate(owner, lastMod, // maxLife, false); // unloadedSipSessions_.put(key, osu); // if (passivate) { // try { // long elapsed = System.currentTimeMillis() - lastMod; // // if maxIdle time configured, means that we need to // // passivate sessions that have // // exceeded the max allowed idle time // if (passivationMax >= 0 && elapsed > passivationMax) { // if (trace_) { // log_.trace("Elapsed time of " + elapsed // + " for session " + key // + " exceeds max of " + passivationMax // + "; passivating"); // } // processUnloadedSipSessionPassivation(key, osu); // } // // If the session didn't exceed the // // passivationMaxIdleTime_, see // // if the number of sessions managed by this manager // // greater than the max allowed // // active sessions, passivate the session if it exceed // // passivationMinIdleTime_ // else if (maxActiveAllowed_ > 0 && passivationMin >= 0 // && calcActiveSessions() > maxActiveAllowed_ // && elapsed >= passivationMin) { // if (trace_) { // log_.trace("Elapsed time of " + elapsed // + " for session " + key // + " exceeds min of " + passivationMin // + "; passivating"); // } // processUnloadedSipSessionPassivation(key, osu); // } // } catch (Exception e) { // // most likely a lock conflict if the session is being // // updated remotely; ignore it // log_.debug("Problem passivating session " + key // + " -- " + e.toString()); // } // } // } // } // } /** * Gets the ids of all sessions in the distributed cache and adds them to * the unloaded sessions map, along with their lastAccessedTime and their * maxInactiveInterval. Passivates overage or excess sessions. */ private void initializeUnloadedSipApplicationSessions() { Map<SipApplicationSessionKey, String> sessions = getDistributedCacheConvergedSipManager().getSipApplicationSessionKeys(); if (sessions != null) { boolean passivate = isPassivationEnabled(); long passivationMax = passivationMaxIdleTime_ * 1000L; long passivationMin = passivationMinIdleTime_ * 1000L; for (Map.Entry<SipApplicationSessionKey, String> entry : sessions.entrySet()) { SipApplicationSessionKey key = entry.getKey(); String owner = entry.getValue(); long ts = -1; DistributableSessionMetadata md = null; try { IncomingDistributableSessionData sessionData = getDistributedCacheConvergedSipManager() .getSessionData(key, owner, false); ts = sessionData.getTimestamp(); md = sessionData.getMetadata(); } catch (Exception e) { // most likely a lock conflict if the session is being // updated remotely; // ignore it and use default values for timstamp and // maxInactive log_.debug("Problem reading metadata for session " + key + " -- " + e.toString()); } long lastMod = ts == -1 ? System.currentTimeMillis() : ts; int maxLife = md == null ? getMaxInactiveInterval() : md .getMaxInactiveInterval(); OwnedSessionUpdate osu = new OwnedSessionUpdate(owner, lastMod, maxLife, false); unloadedSipApplicationSessions_.put(key, osu); if (passivate) { try { long elapsed = System.currentTimeMillis() - lastMod; // if maxIdle time configured, means that we need to // passivate sessions that have // exceeded the max allowed idle time if (passivationMax >= 0 && elapsed > passivationMax) { if (trace_) { log_.trace("Elapsed time of " + elapsed + " for session " + key + " exceeds max of " + passivationMax + "; passivating"); } processUnloadedSipApplicationSessionPassivation(key, osu); } // If the session didn't exceed the // passivationMaxIdleTime_, see // if the number of sessions managed by this manager // greater than the max allowed // active sessions, passivate the session if it exceed // passivationMinIdleTime_ else if (maxActiveAllowed_ > 0 && passivationMin >= 0 && calcActiveSessions() > maxActiveAllowed_ && elapsed >= passivationMin) { if (trace_) { log_.trace("Elapsed time of " + elapsed + " for session " + key + " exceeds min of " + passivationMin + "; passivating"); } processUnloadedSipApplicationSessionPassivation(key, osu); } } catch (Exception e) { // most likely a lock conflict if the session is being // updated remotely; ignore it log_.debug("Problem passivating session " + key + " -- " + e.toString()); } } } } } /** * @return the SipFactoryImpl */ public SipFactoryImpl getSipFactoryImpl() { return sipManagerDelegate.getSipFactoryImpl(); } /** * @param sipFactoryImpl * the SipFactoryImpl to set */ public void setSipFactoryImpl(SipFactoryImpl sipFactoryImpl) { sipManagerDelegate.setSipFactoryImpl(sipFactoryImpl); } /** * @return the container */ public Container getContainer() { return sipManagerDelegate.getContainer(); } /** * @param container * the container to set */ public void setContainer(Container container) { container_ = container; sipManagerDelegate.setContainer(container); } /** * {@inheritDoc} */ public MobicentsSipSession removeSipSession(final SipSessionKey key) { ClusteredSipSession<? extends OutgoingDistributableSessionData> clusterSess = (ClusteredSipSession<? extends OutgoingDistributableSessionData>) sipManagerDelegate.removeSipSession(key); if(clusterSess == null) { return null; } synchronized (clusterSess) { String realId = clusterSess.getId(); if (log_.isDebugEnabled()) { log_.debug("Removing session from store with id: " + realId); } try { clusterSess.removeMyself(); } finally { // We don't want to replicate this session at the end // of the request; the removal process took care of that ConvergedSessionReplicationContext.sipSessionExpired(clusterSess, key, getSnapshotSipManager()); // Track this session to prevent reincarnation by this request // from the distributed cache ConvergedSessionInvalidationTracker.sipSessionInvalidated(key, this); sipManagerDelegate.removeSipSession(key); stats_.removeStats(realId); // Compute how long this session has been alive, and update // our statistics accordingly // int timeAlive = (int) ((System.currentTimeMillis() - clusterSess.getCreationTimeInternal())/1000); // sipSessionExpired(timeAlive); } } return clusterSess; } /** * {@inheritDoc} */ public MobicentsSipApplicationSession removeSipApplicationSession( final SipApplicationSessionKey key) { ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData> clusterSess = (ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData>) sipManagerDelegate.removeSipApplicationSession(key); if(clusterSess == null) { return null; } synchronized (clusterSess) { String realId = clusterSess.getId(); if (log_.isDebugEnabled()) { log_.debug("Removing session from store with id: " + realId); } try { clusterSess.removeMyself(); } finally { // We don't want to replicate this session at the end // of the request; the removal process took care of that ConvergedSessionReplicationContext.sipApplicationSessionExpired(clusterSess, key, getSnapshotSipManager()); // Track this session to prevent reincarnation by this request // from the distributed cache ConvergedSessionInvalidationTracker.sipApplicationSessionInvalidated(key, this); sipManagerDelegate.removeSipApplicationSession(key); stats_.removeStats(realId); // Compute how long this session has been alive, and update // our statistics accordingly // int timeAlive = (int) ((System.currentTimeMillis() - clusterSess.getCreationTimeInternal())/1000); // sipApplicationSessionExpired(timeAlive); } } return clusterSess; } /** * {@inheritDoc} */ public MobicentsSipApplicationSession getSipApplicationSession( final SipApplicationSessionKey key, final boolean create) { // Find it from the local store first ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData> session = findLocalSipApplicationSession(key, false); // If we didn't find it locally, only check the distributed cache // if we haven't previously handled this session id on this request. // If we handled it previously but it's no longer local, that means // it's been invalidated. If we request an invalidated session from // the distributed cache, it will be missing from the local cache but // may still exist on other nodes (i.e. if the invalidation hasn't // replicated yet because we are running in a tx). With buddy // replication, // asking the local cache for the session will cause the out-of-date // session from the other nodes to be gravitated, thus resuscitating // the session. if (session == null && !ConvergedSessionInvalidationTracker.isSessionInvalidated(key.toString(), this)) { if (logger.isDebugEnabled()) log_.debug("Checking for sip app session " + key + " in the distributed cache"); session = loadSipApplicationSession(key, create); if (session != null) { add(session); Iterator<ClusteredSipSession<OutgoingDistributableSessionData>> sipSessionIt = (Iterator<ClusteredSipSession<OutgoingDistributableSessionData>>) ((MobicentsSipApplicationSession)session).getSessions("SIP"); if(logger.isDebugEnabled()) { logger.debug("loading the underlying sip sessions from the cache"); } while (sipSessionIt.hasNext()) { ClusteredSipSession sipSession = (ClusteredSipSession) sipSessionIt .next(); if(logger.isDebugEnabled()) { logger.debug("loading the underlying sip session from the cache " + sipSession.getKey()); } getSipSession(sipSession.getKey(), false, null, session); } // TODO should we advise of a new session? // tellNew(); } } else if (session != null && session.isOutdated()) { if (logger.isDebugEnabled()) log_.debug("Updating sip app session " + key + " from the distributed cache"); // Need to update it from the cache loadSipApplicationSession(key, create); } if (session != null) { if (logger.isDebugEnabled()) logger.debug("Adding sip app session " + key + " for replication"); // Add this session to the set of those potentially needing // replication ConvergedSessionReplicationContext.bindSipApplicationSession(session, getSnapshotSipManager()); // If we previously called passivate() on the session due to // replication, we need to make an offsetting activate() call if (session.getNeedsPostReplicateActivation()) { session.notifyDidActivate(ClusteredSessionNotificationCause.REPLICATION); } } return session; } /** * {@inheritDoc} */ public MobicentsSipSession getSipSession(final SipSessionKey key, final boolean create, final SipFactoryImpl sipFactoryImpl, final MobicentsSipApplicationSession sipApplicationSessionImpl) { // Find it from the local store first ClusteredSipSession<? extends OutgoingDistributableSessionData> session = findLocalSipSession(key, false, sipApplicationSessionImpl); if(session == null) { if (logger.isDebugEnabled()) { logger.debug("sip session " + key + " not found in the local store"); } } else { if (logger.isDebugEnabled()) { logger.debug("sip session " + key + " found in the local store " + session); } } boolean isSipSessionInvalidated = ConvergedSessionInvalidationTracker.isSessionInvalidated(key.toString(), this); if (logger.isDebugEnabled()) { logger.debug("sip session " + key + " invalidated ? " + isSipSessionInvalidated); } // If we didn't find it locally, only check the distributed cache // if we haven't previously handled this session id on this request. // If we handled it previously but it's no longer local, that means // it's been invalidated. If we request an invalidated session from // the distributed cache, it will be missing from the local cache but // may still exist on other nodes (i.e. if the invalidation hasn't // replicated yet because we are running in a tx). With buddy // replication, // asking the local cache for the session will cause the out-of-date // session from the other nodes to be gravitated, thus resuscitating // the session. if (session == null && !isSipSessionInvalidated) { if (logger.isDebugEnabled()) logger.debug("Checking for sip session " + key + " in the distributed cache"); session = loadSipSession(key, create, sipFactoryImpl, sipApplicationSessionImpl); // if (session != null) // { // add(session); // // We now notify, since we've added a policy to allow listeners // // to discriminate. But the default policy will not allow the // // notification to be emitted for FAILOVER, so the standard // // behavior is unchanged. // session.tellNew(ClusteredSessionNotificationCause.FAILOVER); // } } else if (session != null && session.isOutdated()) { if (logger.isDebugEnabled()) logger.debug("Updating sip session " + key + " from the distributed cache"); // Need to update it from the cache loadSipSession(key, create, sipFactoryImpl, sipApplicationSessionImpl); } if (session != null) { if (logger.isDebugEnabled()) logger.debug("Adding sip session " + key + " for replication"); // Add this session to the set of those potentially needing // replication ConvergedSessionReplicationContext.bindSipSession(session, getSnapshotSipManager()); // If we previously called passivate() on the session due to // replication, we need to make an offsetting activate() call if (session.getNeedsPostReplicateActivation()) { session.notifyDidActivate(ClusteredSessionNotificationCause.REPLICATION); } } return session; } /** * Notifies the manager that a session in the distributed cache has been * invalidated * * FIXME This method is poorly named, as we will also get this callback when * we use buddy replication and our copy of the session in JBC is being * removed due to data gravitation. * * @param realId * the session id excluding any jvmRoute */ public void notifyRemoteSipApplicationSessionInvalidation(String realId) { // Remove the session from our local map SipApplicationSessionKey key = null; try { key = SessionManagerUtil.parseSipApplicationSessionKey(realId); } catch (ParseException e) { //should never happen logger.error("An unexpected exception happened on parsing the following sip application session key " + realId, e); return; } ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData> session = (ClusteredSipApplicationSession)sipManagerDelegate .removeSipApplicationSession(key); if (session == null) { // We weren't managing the session anyway. But remove it // from the list of cached sessions we haven't loaded if (unloadedSipApplicationSessions_.remove(key) != null) { if (trace_) log_.trace("Removed entry for session " + key + " from unloaded session map"); } // If session has failed over and has been passivated here, // session will be null, but we'll have a TimeStatistic to clean up stats_.removeStats(realId); } else { // Expire the session // DON'T SYNCHRONIZE ON SESSION HERE -- isValid() and // expire() are meant to be multi-threaded and synchronize // properly internally; synchronizing externally can lead // to deadlocks!! boolean notify = false; // Don't notify listeners. SRV.10.7 // allows this, and sending notifications // leads to all sorts of issues; e.g. // circular calls with ClusteredSSO and // notifying when all that's happening is // data gravitation due to random failover boolean localCall = false; // this call originated from the cache; // we have already removed session boolean localOnly = true; // Don't pass attr removals to cache // Ensure the correct TCL is in place // BES 2008/11/27 Why? // ContextClassLoaderSwitcher.SwitchContext switcher = null; try { // Don't track this invalidation is if it were from a request ConvergedSessionInvalidationTracker.suspend(); // switcher = getContextClassLoaderSwitcher().getSwitchContext(); // switcher.setClassLoader(tcl_); // session.invalidate(notify, localCall, localOnly, // ClusteredSessionNotificationCause.INVALIDATE); session.invalidate(); } finally { ConvergedSessionInvalidationTracker.resume(); // Remove any stats for this session stats_.removeStats(realId); // if (switcher != null) { // switcher.reset(); // } } } } /** * Notifies the manager that a session in the distributed cache has been * invalidated * * FIXME This method is poorly named, as we will also get this callback when * we use buddy replication and our copy of the session in JBC is being * removed due to data gravitation. * * @param realId * the session id excluding any jvmRoute */ public void notifyRemoteSipSessionInvalidation(String realId) { // Remove the session from our local map SipSessionKey key = null; try { key = SessionManagerUtil.parseSipSessionKey(realId); } catch (ParseException e) { //should never happen logger.error("An unexpected exception happened on parsing the following sip session key " + realId, e); return; } ClusteredSipSession<? extends OutgoingDistributableSessionData> session = (ClusteredSipSession)sipManagerDelegate .removeSipSession(key); if (session == null) { // We weren't managing the session anyway. But remove it // from the list of cached sessions we haven't loaded if (unloadedSipSessions_.remove(key) != null) { if (trace_) log_.trace("Removed entry for session " + key + " from unloaded session map"); } // If session has failed over and has been passivated here, // session will be null, but we'll have a TimeStatistic to clean up stats_.removeStats(realId); } else { // Expire the session // DON'T SYNCHRONIZE ON SESSION HERE -- isValid() and // expire() are meant to be multi-threaded and synchronize // properly internally; synchronizing externally can lead // to deadlocks!! boolean notify = false; // Don't notify listeners. SRV.10.7 // allows this, and sending notifications // leads to all sorts of issues; e.g. // circular calls with ClusteredSSO and // notifying when all that's happening is // data gravitation due to random failover boolean localCall = false; // this call originated from the cache; // we have already removed session boolean localOnly = true; // Don't pass attr removals to cache // Ensure the correct TCL is in place // BES 2008/11/27 Why? // ContextClassLoaderSwitcher.SwitchContext switcher = null; try { // Don't track this invalidation is if it were from a request ConvergedSessionInvalidationTracker.suspend(); // switcher = getContextClassLoaderSwitcher().getSwitchContext(); // switcher.setClassLoader(tcl_); // session.expire(notify, localCall, localOnly, // ClusteredSessionNotificationCause.INVALIDATE); session.invalidate(); } finally { ConvergedSessionInvalidationTracker.resume(); // Remove any stats for this session stats_.removeStats(realId); // if (switcher != null) { // switcher.reset(); // } } } } /** * Callback from the distributed cache notifying of a local modification to * a session's attributes. Meant for use with FIELD granularity, where the * session may not be aware of modifications. * * @param realId * the session id excluding any jvmRoute */ public void notifySipApplicationSessionLocalAttributeModification( String realId) { SipApplicationSessionKey key = null; try { key = SessionManagerUtil.parseSipApplicationSessionKey(realId); } catch (ParseException e) { // should never happen logger .error( "An unexpected exception happened on parsing the following sip application session key " + realId, e); return; } ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData> session = (ClusteredSipApplicationSession) sipManagerDelegate .getSipApplicationSession(key, false); if (session != null) { session.sessionAttributesDirty(); } else { log_.warn("Received local attribute notification for " + realId + " but session is not locally active"); } } /** * Callback from the distributed cache notifying of a local modification to * a session's attributes. Meant for use with FIELD granularity, where the * session may not be aware of modifications. * * @param realId * the session id excluding any jvmRoute */ public void notifySipSessionLocalAttributeModification( String realId) { SipSessionKey key = null; try { key = SessionManagerUtil.parseSipSessionKey(realId); } catch (ParseException e) { // should never happen logger .error( "An unexpected exception happened on parsing the following sip session key " + realId, e); return; } ClusteredSipSession<? extends OutgoingDistributableSessionData> session = (ClusteredSipSession) sipManagerDelegate .getSipSession(key, false, null, null); if (session != null) { session.sessionAttributesDirty(); } else { log_.warn("Received local attribute notification for " + realId + " but session is not locally active"); } } /** * Callback from the distributed cache to notify us that a session has been * modified remotely. * * @param realId * the session id, without any trailing jvmRoute * @param dataOwner * the owner of the session. Can be <code>null</code> if the * owner is unknown. * @param distributedVersion * the session's version per the distributed cache * @param timestamp * the session's timestamp per the distributed cache * @param metadata * the session's metadata per the distributed cache */ public boolean sipApplicationSessionChangedInDistributedCache( String realId, String dataOwner, int distributedVersion, long timestamp, DistributableSessionMetadata metadata) { boolean updated = true; SipApplicationSessionKey key = null; try { key = SessionManagerUtil.parseSipApplicationSessionKey(realId); } catch (ParseException e) { // should never happen logger .error( "An unexpected exception happened on parsing the following sip application session key " + realId, e); return false; } ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData> session = findLocalSipApplicationSession( key, false); if (session != null) { // Need to invalidate the loaded session. We get back whether // this an actual version increment updated = session .setVersionFromDistributedCache(distributedVersion); if (updated && trace_) { log_.trace("session in-memory data is invalidated for id: " + realId + " new version: " + distributedVersion); } } else { int maxLife = metadata == null ? getMaxInactiveInterval() : metadata.getMaxInactiveInterval(); Object existing = unloadedSipApplicationSessions_ .put(key, new OwnedSessionUpdate(dataOwner, timestamp, maxLife, false)); if (existing == null) { calcActiveSessions(); if (trace_) { log_.trace("New session " + realId + " added to unloaded session map"); } } else if (trace_) { log_.trace("Updated timestamp for unloaded session " + realId); } } return updated; } public void sipApplicationSessionActivated() { int pc = sipApplicationSessionPassivatedCount_.decrementAndGet(); // Correct for drift since we don't know the true passivation // count when we started. We can get activations of sessions // we didn't know were passivated. // FIXME -- is the above statement still correct? Is this needed? if (pc < 0) { // Just reverse our decrement. sipApplicationSessionPassivatedCount_.incrementAndGet(); } } public void sipSessionActivated() { int pc = sipSessionPassivatedCount_.decrementAndGet(); // Correct for drift since we don't know the true passivation // count when we started. We can get activations of sessions // we didn't know were passivated. // FIXME -- is the above statement still correct? Is this needed? if (pc < 0) { // Just reverse our decrement. sipSessionPassivatedCount_.incrementAndGet(); } } /** * Callback from the distributed cache to notify us that a session has been * modified remotely. * * @param sipSessionId * the session id, without any trailing jvmRoute * @param dataOwner * the owner of the session. Can be <code>null</code> if the * owner is unknown. * @param distributedVersion * the session's version per the distributed cache * @param timestamp * the session's timestamp per the distributed cache * @param metadata * the session's metadata per the distributed cache */ public boolean sipSessionChangedInDistributedCache( String sipAppSessionId, String sipSessionId, String dataOwner, int distributedVersion, long timestamp, DistributableSessionMetadata metadata) { boolean updated = true; SipApplicationSessionKey sipAppSessionKey = null; try { sipAppSessionKey = SessionManagerUtil.parseSipApplicationSessionKey(sipAppSessionId); } catch (ParseException e) { // should never happen logger .error( "An unexpected exception happened on parsing the following sip application session key " + sipAppSessionId, e); return false; } SipSessionKey key = null; try { key = SessionManagerUtil.parseSipSessionKey(sipSessionId); } catch (ParseException e) { // should never happen logger .error( "An unexpected exception happened on parsing the following sip session key " + sipSessionId, e); return false; } ClusteredSipSession<? extends OutgoingDistributableSessionData> session = findLocalSipSession( key, false, null); if (session != null) { // Need to invalidate the loaded session. We get back whether // this an actual version increment updated = session .setVersionFromDistributedCache(distributedVersion); if (updated && trace_) { log_.trace("session in-memory data is invalidated for id: " + sipSessionId + " new version: " + distributedVersion); } } else { int maxLife = metadata == null ? getMaxInactiveInterval() : metadata.getMaxInactiveInterval(); Object existing = unloadedSipSessions_ .put(new ClusteredSipSessionKey(key, sipAppSessionKey), new OwnedSessionUpdate(dataOwner, timestamp, maxLife, false)); if (existing == null) { calcActiveSessions(); if (trace_) { log_.trace("New session " + sipSessionId + " added to unloaded session map"); } } else if (trace_) { log_.trace("Updated timestamp for unloaded session " + sipSessionId); } } return updated; } /** * {@inheritDoc} */ public MobicentsSipApplicationSession findSipApplicationSession( HttpSession httpSession) { return sipManagerDelegate.findSipApplicationSession(httpSession); } /** * */ public void dumpSipSessions() { sipManagerDelegate.dumpSipSessions(); } /** * */ public void dumpSipApplicationSessions() { sipManagerDelegate.dumpSipApplicationSessions(); } /** * {@inheritDoc} */ public Iterator<MobicentsSipSession> getAllSipSessions() { return sipManagerDelegate.getAllSipSessions(); } /** * {@inheritDoc} */ public Iterator<MobicentsSipApplicationSession> getAllSipApplicationSessions() { return sipManagerDelegate.getAllSipApplicationSessions(); } /** * {@inheritDoc} */ public void removeAllSessions() { sipManagerDelegate.removeAllSessions(); } @Override public void stop() throws LifecycleException { // Handle re-entrance if (this.semaphore.tryAcquire()) { try { log_.debug("Closing off LockingValve"); // Acquire all remaining permits, shutting off locking valve this.semaphore.acquire(TOTAL_PERMITS - 1); } catch (InterruptedException e) { this.semaphore.release(); throw new LifecycleException(e); } } clearSessions(); unloadedSessions_.clear(); passivatedCount_.set(0); super.stop(); } @Override protected void startExtensions() { super.startExtensions(); initializeUnloadedSipApplicationSessions(); // initializeUnloadedSipSessions(); initClusteredSipSessionNotificationPolicy(); initClusteredSipApplicationSessionNotificationPolicy(); // Handle re-entrance if (!this.semaphore.tryAcquire()) { log_.debug("Opening up LockingValve"); // Make all permits available to locking valve this.semaphore.release(TOTAL_PERMITS); } else { // Release the one we just acquired this.semaphore.release(); } } @Override protected void stopExtensions() { super.stopExtensions(); removeAllSessions(); sipSessionPassivatedCount_.set(0); sipApplicationSessionPassivatedCount_.set(0); unloadedSipApplicationSessions_.clear(); unloadedSipSessions_.clear(); } /** * Create and start a snapshot manager. */ @Override protected void initSnapshotManager() { String ctxPath = ((Context) container_).getPath(); if (SnapshotMode.INSTANT == getSnapshotMode()) { setSnapshotManager(new InstantConvergedSnapshotManager(this, ctxPath)); } else if (getSnapshotMode() == null) { log_.warn("Snapshot mode must be 'instant' or 'interval' - " + "using 'instant'"); setSnapshotMode(SnapshotMode.INSTANT); setSnapshotManager(new InstantConvergedSnapshotManager(this, ctxPath)); } else if (ReplicationGranularity.FIELD == getReplicationGranularity()) { throw new IllegalStateException("Property snapshotMode must be " + SnapshotMode.INTERVAL + " when FIELD granularity is used"); } else if (getSnapshotInterval() < 1) { log_ .warn("Snapshot mode set to 'interval' but snapshotInterval is < 1 " + "using 'instant'"); setSnapshotMode(SnapshotMode.INSTANT); setSnapshotManager(new InstantConvergedSnapshotManager(this, ctxPath)); } else { setSnapshotManager(new IntervalConvergedSnapshotManager(this, ctxPath, getSnapshotInterval())); } getSnapshotManager().start(); } /** * Instantiate a SnapshotManager and ClusteredSessionValve and add the valve * to our parent Context's pipeline. Add a JvmRouteValve and * BatchReplicationClusteredSessionValve if needed. */ protected void installValves() { log_.debug("Adding LockingValve"); this.installValve(new LockingValve(this.valveLock)); // If JK usage wasn't explicitly configured, default to enabling // it if jvmRoute is set on our containing Engine if (!getUseJK()) { setUseJK(Boolean.valueOf(getJvmRoute() != null)); } if (getUseJK()) { log_ .debug("We are using JK for load-balancing. Adding JvmRouteValve."); this.installValve(new ConvergedJvmRouteValve(this)); } // Handle batch replication if needed. // TODO -- should we add this even if not FIELD in case a cross-context // call traverses a field-based webapp? BatchingManager valveBM = null; if (getReplicationGranularity() == ReplicationGranularity.FIELD && Boolean.TRUE.equals(getReplicationConfig().getReplicationFieldBatchMode())) { valveBM = getDistributedCacheConvergedSipManager().getBatchingManager();; log_ .debug("Including transaction manager in ClusteredSessionValve to support batch replication."); } // Add clustered session valve ConvergedClusteredSessionValve valve = new ConvergedClusteredSessionValve(this, valveBM); log_.debug("Adding ConvergedClusteredSessionValve"); this.installValve(valve); } protected void installValve(Valve valve) { boolean installed = false; // In embedded mode, install the valve via JMX to be consistent // with the way the overall context is created in TomcatDeployer. // We can't do this in unembedded mode because we are called // before our Context is registered with the MBean server if (embedded_) { ObjectName name = this.getObjectName(this.container_); if (name != null) { try { MBeanServer server = this.getMBeanServer(); server.invoke(name, "addValve", new Object[] { valve }, new String[] { Valve.class.getName() }); installed = true; } catch (Exception e) { // JBAS-2422. If the context is restarted via JMX, the above // JMX call will fail as the context will not be registered // when it's made. So we catch the exception and fall back // to adding the valve directly. // TODO consider skipping adding via JMX and just do it // directly log_.debug("Caught exception installing valve to Context", e); } } } if (!installed) { // If possible install via the ContainerBase.addValve() API. if (this.container_ instanceof ContainerBase) { ((ContainerBase) this.container_).addValve(valve); } else { // No choice; have to add it to the context's pipeline this.container_.getPipeline().addValve(valve); } } } private ObjectName getObjectName(Container container) { String oname = container.getObjectName(); try { return (oname == null) ? null : new ObjectName(oname); } catch (MalformedObjectNameException e) { log_.warn("Error creating object name from string " + oname, e); return null; } } // JMX Statistics /** * Return descriptive information about this Manager implementation and the * corresponding version number, in the format * <code><description>/<version></code>. */ public String getInfo() { return (info_); } /** * Return the maximum number of active Sessions allowed, or -1 for no limit. */ public int getMaxActiveSipSessions() { return (this.sipManagerDelegate.getMaxActiveSipSessions()); } /** * Set the maximum number of actives Sip Sessions allowed, or -1 for no * limit. * * @param max * The new maximum number of sip sessions */ public void setMaxActiveSipSessions(int max) { this.sipManagerDelegate.setMaxActiveSipSessions(max); } /** * Return the maximum number of active Sessions allowed, or -1 for no limit. */ public int getMaxActiveSipApplicationSessions() { return (this.sipManagerDelegate.getMaxActiveSipApplicationSessions()); } /** * Set the maximum number of actives Sip Application Sessions allowed, or -1 * for no limit. * * @param max * The new maximum number of sip application sessions */ public void setMaxActiveSipApplicationSessions(int max) { this.sipManagerDelegate.setMaxActiveSipApplicationSessions(max); } /** * Number of sip session creations that failed due to maxActiveSipSessions * * @return The count */ public int getRejectedSipSessions() { return sipManagerDelegate.getRejectedSipSessions(); } public void setRejectedSipSessions(int rejectedSipSessions) { this.sipManagerDelegate.setRejectedSipSessions(rejectedSipSessions); } /** * Number of sip session creations that failed due to maxActiveSipSessions * * @return The count */ public int getRejectedSipApplicationSessions() { return sipManagerDelegate.getRejectedSipApplicationSessions(); } public void setRejectedSipApplicationSessions( int rejectedSipApplicationSessions) { this.sipManagerDelegate.setRejectedSipApplicationSessions(rejectedSipApplicationSessions); } public void setSipSessionCounter(int sipSessionCounter) { this.sipManagerDelegate.setSipSessionCounter(sipSessionCounter); } /** * Total sessions created by this manager. * * @return sessions created */ public int getSipSessionCounter() { return sipManagerDelegate.getSipSessionCounter(); } /** * Returns the number of active sessions * * @return number of sessions active */ public int getActiveSipSessions() { return sipManagerDelegate.getNumberOfSipSessions(); } /** * Gets the longest time (in seconds) that an expired session had been * alive. * * @return Longest time (in seconds) that an expired session had been alive. */ public int getSipSessionMaxAliveTime() { return sipManagerDelegate.getSipSessionMaxAliveTime(); } /** * Sets the longest time (in seconds) that an expired session had been * alive. * * @param sessionMaxAliveTime * Longest time (in seconds) that an expired session had been * alive. */ public void setSipSessionMaxAliveTime(int sipSessionMaxAliveTime) { this.sipManagerDelegate.setSipSessionMaxAliveTime(sipSessionMaxAliveTime); } /** * Gets the average time (in seconds) that expired sessions had been alive. * * @return Average time (in seconds) that expired sessions had been alive. */ public int getSipSessionAverageAliveTime() { return sipManagerDelegate.getSipSessionAverageAliveTime(); } /** * Sets the average time (in seconds) that expired sessions had been alive. * * @param sessionAverageAliveTime * Average time (in seconds) that expired sessions had been * alive. */ public void setSipSessionAverageAliveTime(int sipSessionAverageAliveTime) { this.sipManagerDelegate.setSipSessionAverageAliveTime(sipSessionAverageAliveTime); } public void setSipApplicationSessionCounter(int sipApplicationSessionCounter) { this.sipManagerDelegate.setSipApplicationSessionCounter(sipApplicationSessionCounter); } /** * Total sessions created by this manager. * * @return sessions created */ public int getSipApplicationSessionCounter() { return sipManagerDelegate.getSipApplicationSessionCounter(); } /** * Returns the number of active sessions * * @return number of sessions active */ public int getActiveSipApplicationSessions() { return sipManagerDelegate.getNumberOfSipApplicationSessions(); } /** * Gets the longest time (in seconds) that an expired session had been * alive. * * @return Longest time (in seconds) that an expired session had been alive. */ public int getSipApplicationSessionMaxAliveTime() { return sipManagerDelegate.getSipApplicationSessionMaxAliveTime(); } /** * Sets the longest time (in seconds) that an expired session had been * alive. * * @param sessionMaxAliveTime * Longest time (in seconds) that an expired session had been * alive. */ public void setSipApplicationSessionMaxAliveTime( int sipApplicationSessionMaxAliveTime) { this.sipManagerDelegate.setSipApplicationSessionMaxAliveTime(sipApplicationSessionMaxAliveTime); } /** * Gets the average time (in seconds) that expired sessions had been alive. * * @return Average time (in seconds) that expired sessions had been alive. */ public int getSipApplicationSessionAverageAliveTime() { return sipManagerDelegate.getSipApplicationSessionAverageAliveTime(); } /** * Sets the average time (in seconds) that expired sessions had been alive. * * @param sessionAverageAliveTime * Average time (in seconds) that expired sessions had been * alive. */ public void setSipApplicationSessionAverageAliveTime( int sipApplicationSessionAverageAliveTime) { this.sipManagerDelegate.setSipApplicationSessionAverageAliveTime(sipApplicationSessionAverageAliveTime); } /** * Gets the number of sessions that have expired. * * @return Number of sessions that have expired */ public int getExpiredSipSessions() { return sipManagerDelegate.getExpiredSipSessions(); } /** * Sets the number of sessions that have expired. * * @param expiredSessions * Number of sessions that have expired */ public void setExpiredSipSessions(int expiredSipSessions) { this.sipManagerDelegate.setExpiredSipSessions(expiredSipSessions); } /** * Gets the number of sessions that have expired. * * @return Number of sessions that have expired */ public int getExpiredSipApplicationSessions() { return sipManagerDelegate.getExpiredSipApplicationSessions(); } /** * Sets the number of sessions that have expired. * * @param expiredSessions * Number of sessions that have expired */ public void setExpiredSipApplicationSessions( int expiredSipApplicationSessions) { this.sipManagerDelegate.setExpiredSipApplicationSessions(expiredSipApplicationSessions); } /** * {@inheritDoc} */ public long getMaxPassivatedSipSessionCount() { return sipSessionMaxPassivatedCount_.get(); } /** * {@inheritDoc} */ public long getPassivatedSipSessionCount() { return sipSessionPassivatedCount_.get(); } /** * {@inheritDoc} */ public long getMaxPassivatedSipApplicationSessionCount() { return sipApplicationSessionMaxPassivatedCount_.get(); } /** * {@inheritDoc} */ public long getPassivatedSipApplicationSessionCount() { return sipApplicationSessionPassivatedCount_.get(); } private class SipSessionPassivationCheck implements Comparable<SipSessionPassivationCheck> { private final ClusteredSipSessionKey key; private final OwnedSessionUpdate osu; private final ClusteredSipSession<? extends OutgoingDistributableSessionData> session; private SipSessionPassivationCheck(ClusteredSipSessionKey key, OwnedSessionUpdate osu) { assert osu != null : "osu is null"; assert key != null : "key is null"; this.key = key; this.osu = osu; this.session = null; } private SipSessionPassivationCheck(ClusteredSipSession<? extends OutgoingDistributableSessionData> session) { assert session != null : "session is null"; this.key = new ClusteredSipSessionKey(session.getKey(), session.getSipApplicationSession().getKey()); this.session = session; this.osu = null; } private long getLastUpdate() { return osu == null ? session.getLastAccessedTime() : osu.updateTime; } private void passivate() { if (osu == null) { JBossCacheSipManager.this.processSipSessionPassivation(key.getSipSessionKey()); } else { JBossCacheSipManager.this.processUnloadedSipSessionPassivation(key, osu); } } private ClusteredSipSessionKey getKey() { return key; } private boolean isUnloaded() { return osu != null; } // This is what causes sorting based on lastAccessed public int compareTo(SipSessionPassivationCheck o) { long thisVal = getLastUpdate(); long anotherVal = o.getLastUpdate(); return (thisVal<anotherVal ? -1 : (thisVal==anotherVal ? 0 : 1)); } } private class SipApplicationSessionPassivationCheck implements Comparable<SipApplicationSessionPassivationCheck> { private final SipApplicationSessionKey key; private final OwnedSessionUpdate osu; private final ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData> session; private SipApplicationSessionPassivationCheck(SipApplicationSessionKey key, OwnedSessionUpdate osu) { assert osu != null : "osu is null"; assert key != null : "key is null"; this.key = key; this.osu = osu; this.session = null; } private SipApplicationSessionPassivationCheck(ClusteredSipApplicationSession<? extends OutgoingDistributableSessionData> session) { assert session != null : "session is null"; this.key = session.getKey(); this.session = session; this.osu = null; } private long getLastUpdate() { return osu == null ? session.getLastAccessedTime() : osu.updateTime; } private void passivate() { if (osu == null) { JBossCacheSipManager.this.processSipApplicationSessionPassivation(key); } else { JBossCacheSipManager.this.processUnloadedSipApplicationSessionPassivation(key, osu); } } private SipApplicationSessionKey getKey() { return key; } private boolean isUnloaded() { return osu != null; } // This is what causes sorting based on lastAccessed public int compareTo(SipApplicationSessionPassivationCheck o) { long thisVal = getLastUpdate(); long anotherVal = o.getLastUpdate(); return (thisVal<anotherVal ? -1 : (thisVal==anotherVal ? 0 : 1)); } } private class OwnedSessionUpdate { String owner; long updateTime; int maxInactive; boolean passivated; OwnedSessionUpdate(String owner, long updateTime, int maxInactive, boolean passivated) { this.owner = owner; this.updateTime = updateTime; this.maxInactive = maxInactive; this.passivated = passivated; } } public ClusteredSipApplicationSessionNotificationPolicy getSipApplicationSessionNotificationPolicy() { return sipApplicationSessionNotificationPolicy_; } public ClusteredSipSessionNotificationPolicy getSipSessionNotificationPolicy() { return sipSessionNotificationPolicy_; } /** * @return the snapshotSipManager_ */ public SnapshotSipManager getSnapshotSipManager() { return (SnapshotSipManager)getSnapshotManager(); } private static class SemaphoreLock implements Lock { private final Semaphore semaphore; SemaphoreLock(Semaphore semaphore) { this.semaphore = semaphore; } /** * @see java.util.concurrent.locks.Lock#lock() */ public void lock() { this.semaphore.acquireUninterruptibly(); } /** * @see java.util.concurrent.locks.Lock#lockInterruptibly() */ public void lockInterruptibly() throws InterruptedException { this.semaphore.acquire(); } /** * @see java.util.concurrent.locks.Lock#newCondition() */ public Condition newCondition() { throw new UnsupportedOperationException(); } /** * @see java.util.concurrent.locks.Lock#tryLock() */ public boolean tryLock() { return this.semaphore.tryAcquire(); } /** * @see java.util.concurrent.locks.Lock#tryLock(long, java.util.concurrent.TimeUnit) */ public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return this.semaphore.tryAcquire(timeout, unit); } /** * @see java.util.concurrent.locks.Lock#unlock() */ public void unlock() { this.semaphore.release(); } } private class PassivationCheck implements Comparable<PassivationCheck> { private final String realId; private final OwnedSessionUpdate osu; private final ClusteredSession<? extends OutgoingDistributableSessionData> session; private PassivationCheck(String realId, OwnedSessionUpdate osu) { assert osu != null : "osu is null"; assert realId != null : "realId is null"; this.realId = realId; this.osu = osu; this.session = null; } private PassivationCheck(ClusteredSession<? extends OutgoingDistributableSessionData> session) { assert session != null : "session is null"; this.realId = session.getRealId(); this.session = session; this.osu = null; } private long getLastUpdate() { return osu == null ? session.getLastAccessedTimeInternal() : osu.updateTime; } private void passivate() { if (osu == null) { JBossCacheSipManager.this.processSessionPassivation(realId); } else { JBossCacheSipManager.this.processUnloadedSessionPassivation(realId, osu); } } private String getRealId() { return realId; } private boolean isUnloaded() { return osu != null; } // This is what causes sorting based on lastAccessed public int compareTo(PassivationCheck o) { long thisVal = getLastUpdate(); long anotherVal = o.getLastUpdate(); return (thisVal<anotherVal ? -1 : (thisVal==anotherVal ? 0 : 1)); } } }