[Logo] Terracotta Discussion Forums (LEGACY READ-ONLY ARCHIVE)
  [Search] Search   [Recent Topics] Recent Topics   [Members]  Member Listing   [Groups] Back to home page 
[Register] Register / 
[Login] Login 
[Expert]
Programatic Session invalidation + Thousands of empty sessions  XML
Forum Index -> Terracotta for Web Sessions
Author Message
shortmem

journeyman

Joined: 12/12/2011 07:59:13
Messages: 17
Offline

Hello, I wonder if there is a way to programmatically interact with Terracotta sessions.

My application has 2 requirements that has given me a lot of headaches, but I somehow managed to handle them:

  • No concurrent login
  • Admin must have an interface to manually expire a session


    I'm using spring-security and my app is deployed under jboss-as 4.3.

    For the first requirement, I had to create a cluster-aware SessionRegistry so that a spring would work in a clustered environment. Plus, I had to extend the SessionInformation object in order to notify the cache once an object was mutated (Unfortunately spring code mutates the objects, it's not up to the service only - it assumes everything is in memory, but to reflect the changes on EhCache I must deal with Serialization caveats).

    For the second requirement I created a JMX service that delegates invalidation to the JMX Manager of my application (jboss.web:host=localhost,path=/myapp,type=Manager) so that I can invoke the expireSession method by passing a sessionID.

    The problem is: Even though the application is behaving correctly, the sessions are NOT expired in terracotta.

    To test this behavior I did the following:

    1)Logged in with a user in my app
    2)Tried to log again in the same node (Spring Concurrent login worked)
    3)Tried to log again in from another node (Spring Concurrent login worked)
    4)Expired the session
    5)Repeat 1-3 (OK)
    6)I still can see the session created on step 1 on dev-console plus some empty sessions

    From my understanding it seems that the JBoss Session Manager does NOT play nicelly with Terracotta's sessions. Is there any out of the box jboss service that can handle this?

    Also, I can see TONS of "empty" sessions on dev-console (once I select the id the Attribute/Value table is empty). I believe some of them are actually created by spring (concurrent login control replaces the initial session after a successful login), but I don't understand why they're hanging there forever. After a week there are more than 80000 objects even if no one uses the Application in the weekend.

    Is there any programatic way to actually access these dead sessions? My real sessions are tagged with some attributes, so I could filter out the empty sessions from the real ones in order to remove them.
  • shortmem

    journeyman

    Joined: 12/12/2011 07:59:13
    Messages: 17
    Offline

    OK I managed to get a standalone APP to connect to jmx-server and attempt to invalidate the sessions.

    Unfortunately I can't use this code in my web-application since I had to put the whole tc.jar in this standalone app classpath to use the jmxmp protocol. Is there any other way to connect programmatically to the JMX server?

    Code:
     import java.io.IOException;
     import java.util.ArrayList;
     import java.util.Arrays;
     import java.util.List;
     import java.util.Map;
     import java.util.Set;
     
     import javax.management.MBeanServerConnection;
     import javax.management.MBeanServerInvocationHandler;
     import javax.management.ObjectName;
     import javax.management.remote.JMXConnector;
     import javax.management.remote.JMXConnectorFactory;
     import javax.management.remote.JMXServiceURL;
     
     //copied from the tim inside sessions.jar
     import com.terracotta.session.management.SessionMonitorMBean;
     
     public class TCJMXSessionHelper {
     
     	String connectorUrl = "service:jmx:jmxmp://172.16.100.2:9520";
     
     	String appPattern = "org.terracotta:appName=localhost/myapp";
     
     	private JMXConnector getJMXConnector() throws IOException {
     		return getJMXConnector(connectorUrl);
     	}
     
     	JMXConnector getJMXConnector(final String url) throws IOException {
     		return JMXConnectorFactory.connect(new JMXServiceURL(url));
     	}
     
     	List<SessionMonitorMBean> getSessionMbeans(final JMXConnector connector) throws IOException {
     
     		final List<SessionMonitorMBean> ret = new ArrayList<>();
     		final MBeanServerConnection connection = connector.getMBeanServerConnection();
     
     		final Set<ObjectName> names = connection.queryNames(null, null);
     
     		for (final ObjectName on : names) {
     			if (on.toString().startsWith(appPattern)) {
     				ret.add(MBeanServerInvocationHandler.newProxyInstance(connection, on, SessionMonitorMBean.class, false));
     			}
     		}
     
     		return ret;
     	}
     
     	public void purgeSessions(final Iterable<String> ids) throws IOException {
     
     		final JMXConnector connector = getJMXConnector();
     
     		try {
     			final List<SessionMonitorMBean> mbeans = getSessionMbeans(connector);
     
     			for (final SessionMonitorMBean bean : mbeans) {
     				try {
     					for (final String id : ids) {
     						bean.expireSession(id);
     					}
     					break;
     				} catch (final Exception ex) {
     					// ignore go to next bean
     				}
     
     			}
     
     		} finally {
     			connector.close();
     		}
     
     	}
     
     	public void purgeSessions(final String... ids) throws IOException {
     		purgeSessions(Arrays.asList(ids));
     	}
     
     	public void purgeStaleSesssions() throws IOException {
     
     		final JMXConnector connector = getJMXConnector();
     
     		try {
     			final List<SessionMonitorMBean> mbeans = getSessionMbeans(connector);
     
     			beanLoop: for (final SessionMonitorMBean bean : mbeans) {
     				final int step = 100;
     				final long total = bean.getActiveSessionCount();
     
     				try {
     					for (long j = step; j <= total + step; j += step) {
     						final Set<String> ids = bean.getSessionIds(step);
     
     						if (ids == null || ids.isEmpty()) {
     							break beanLoop;
     						}
     
     						for (final String id : ids) {
     							final Map<String, String> map = bean.getSessionAttributes(id);
     
     							if (map == null || map.isEmpty()) {
     								if (!bean.expireSession(id)) {
     									System.out.println("Why can't TC expire " + id + " ????");
     								}
     							}
     						}
     					}
     					break;
     				} catch (final Exception ex) {
     					// ignore go to next bean
     				}
     			}
     		} finally {
     			connector.close();
     		}
     	}
     
     }
     
     


    What are the conditions for the expireSession method to return false?

    When I create a session, it is successfully expired via this jmx helper, however these "blank" sessions can't be expired!!! Even in dev-console (which I believe uses a similar code) they can't be expired at all!
    shortmem

    journeyman

    Joined: 12/12/2011 07:59:13
    Messages: 17
    Offline

    Well ok...hacked in the TerracottaSessionManager code...

    Code:
      public boolean killSession(String browserSessionId) {
         SessionId id = this.idGenerator.makeInstanceFromBrowserId(browserSessionId);
         if (id == null)
         {
           id = this.idGenerator.makeInstanceFromInternalKey(browserSessionId);
         }
         return expire(id);
       }
     
     private boolean expire(SessionId id) {
         boolean result = false;
         SessionData sd = null;
         boolean locked = false;
         try {
           sd = this.store.find(id);
           if (sd != null) {
             if (!isSessionLockingEnabled()) {
               id.getWriteLock();
             }
             locked = true;
             expire(id, sd);
             result = true;
           }
         } finally {
           if ((sd != null) && (locked)) {
             id.commitWriteLock();
           }
         }
         return result;
       }
     


    It seems that there's no SessionData associated with the id, but since the id keeps showing in dev-console I assume it's is dangling somewhere in the sessionData (a ConcurrentDistributtedMap) keyset:

    Code:
      public Set<String> getSessionIds(int batchSize) {
         int count = 0;
         HashSet result = new HashSet();
         for (String id : this.store.getAllKeys()) {
           result.add(id);
           if (count++ >= batchSize) {
             break;
           }
         }
         return result;
       }
     



    Is this not possibly a memory leak? Perhaps the code should force the removal of these ids from the store:

    Code:
     private boolean expire(SessionId id) {
         boolean result = false;
         SessionData sd = null;
         boolean locked = false;
         try {
           sd = this.store.find(id);
           if (sd != null) {
             if (!isSessionLockingEnabled()) {
               id.getWriteLock();
             }
             locked = true;
             expire(id, sd);
             result = true;
           }else{
                //FORCE REMOVE
               this.store.remove(id);
            }
         } finally {
           if ((sd != null) && (locked)) {
             id.commitWriteLock();
           }
         }
         return result;
       }
     
     
    rajoshi

    seraphim

    Joined: 07/04/2011 04:36:10
    Messages: 1491
    Offline

    Can you please raise a community Jira for this here with all the details :

    https://jira.terracotta.org/jira/browse

    Rakesh Joshi
    Senior Consultant
    Terracotta.
    shortmem

    journeyman

    Joined: 12/12/2011 07:59:13
    Messages: 17
    Offline

    rajoshi wrote:
    Can you please raise a community Jira for this here with all the details :

    https://jira.terracotta.org/jira/browse 


    Posted https://jira.terracotta.org/jira/browse/CDV-1650
     
    Forum Index -> Terracotta for Web Sessions
    Go to:   
    Powered by JForum 2.1.7 © JForum Team