All,

On 3/22/24 09:59, Christopher Schultz wrote:
All,

On 3/22/24 09:33, Robert Turner wrote:
On Fri, Mar 22, 2024 at 9:28 AM Christopher Schultz <
ch...@christopherschultz.net> wrote:

Robert,

On 3/21/24 15:31, Robert Turner wrote:
We receive the sessionWillPassivate and sessionDidActivate callbacks
on startup. Odd that you are not. That's how we achieve the same.
On 3/21/24 16:21, Robert Turner wrote:
Just to add a bit more information, our handler class, for better or for
worse, implements the following interfaces all in one class:

   implements HttpSessionBindingListener, HttpSessionActivationListener,
HttpSessionIdListener, HttpSessionListener, ServletContextListener

Hmm.

I'm already using HttpSessionListener and HttpSessionActivationListener
and logging every event I receive.

HttpSessionIdListener only lets you know when ids are changed, and I
actually don't care about those events. I added it, and see no change in
behavior.

HttpSessionBindingListener shouldn't do anything, here, as it will only
be called when objects are added or removed (and it only *that object*).
During activation and passivation, I wouldn't expect anything to be
added or removed.

ServletContextListener wouldn't do anything in and of itself, except
possibly get the listener started earlier. I added it and do not see any
change in behavior.


Yeah, I wasn't really suggesting adding all those listener interfaces --
more just saying that's what we did in case somehow it made a difference
for you. Certainly you shouldn't have to add them to get it to work.



We also use that same class as our "session model" object that we bind as
an attribute to the session itself (it's a bit of a mixed bag
historically
that I want to clean up).

And in terms of registration, we do not have any annotations on the
class,
instead we register it in web.xml (in the application WAR file) using a
standard listener entry:

<listener>
      <listener-class><<class name>></listener-class>
</listener>

Our web.xml is set at Servlet API version 3.0 (kind-of old), and we are
running against Tomcat 9.5 (and this worked on 8.5 and before as well).

Not sure if that adds anything Chris that you haven't already looked at.

I believe mine is set up identically to yours at this point, except for
the HttpSessionBindingListener.

I would really prefer a way to query the sessions from the app, but as we
know, that's not part of the current Servlet specification, or any
extensions Tomcat currently provides.

It wouldn't really be appropriate for Tomcat to provide any "extensions"
like this because it would make applications reliant on capabilities
that aren't standard. When companies do that, it's called "vendor
lock-in" and it's not a good look for ASF.


Yeah, vendor lock-in isn't great -- and I wouldn't really suggest Tomcat
doing that either; it would be better in the Servlet specification, but I
doubt, for various reasons, it would get added.

Your case is certainly odd -- I suppose you might have to resort to firing
up a debugger and debug build and seeing what's going on in Tomcat...(at
least you are more used to doing that on Tomcat than most of us).

So... by went ahead and loaded-up this class with *everything* - including putting the listener class instance into every session and I do indeed get "session will passivate" and "session has activated" log messgaes.

So I started removing things and it kept working until ... it didn't.

I'm trying to track-down exactly what difference makes it work, but it doesn't many any sense to me.

The StandardManager code looks like this:

   if (log.isTraceEnabled()) {
       log.trace("Loading " + n + " persisted sessions");
   }
   for (int i = 0; i < n; i++) {
       StandardSession session = getNewSession();
       session.readObjectData(ois);
       session.setManager(this);
       sessions.put(session.getIdInternal(), session);
       session.activate();
       if (!session.isValidInternal()) {
           // If session is already invalid,
           // expire session to prevent memory leak.
           session.setValid(true);
           session.expire();
       }
       sessionCounter++;
   }

and StandardSession.activate looks like this:

     public void activate() {

         // Initialize access count
         if (ACTIVITY_CHECK) {
             accessCount = new AtomicInteger();
         }

         // Notify interested session event listeners
         fireSessionEvent(SESSION_ACTIVATED_EVENT, null);

         // Notify ActivationListeners
         HttpSessionEvent event = null;
         String keys[] = keys();
         for (String key : keys) {
             Object attribute = attributes.get(key);
             if (attribute instanceof HttpSessionActivationListener) {
                 if (event == null) {
                     event = new HttpSessionEvent(getSession());
                 }
                 try {
                    ((HttpSessionActivationListener) attribute).sessionDidActivate(event);
                 } catch (Throwable t) {
                     ExceptionUtils.handleThrowable(t);

manager.getContext().getLogger().error(sm.getString("standardSession.attributeEvent"),
 t);
                 }
             }
         }
     }

StandardSession.fireSessionEvent:

     public void fireSessionEvent(String type, Object data) {
         if (listeners.size() < 1) {
             return;
         }
         SessionEvent event = new SessionEvent(this, type, data);
         SessionListener list[] = new SessionListener[0];
         synchronized (listeners) {
             list = listeners.toArray(list);
         }

         for (SessionListener sessionListener : list) {
             sessionListener.sessionEvent(event);
         }

     }

In this case, SessionListener is a Tomcat interface and has nothing to do with the Servlet Specification, so I can ignore that.

So I think what is happening is that it's really the HttpSessionBindingListener that's causing this to work.

Time to read the specs and javadoc more closely to see if maybe it was just my expectations that were wrong.

Okay, this was apparently just my own ignorance about how these interfaces work. My expectation was that HttpSessionActivationListener was a listener that would be notified by the container any time a session was going to be passivated or activated. I was wrong. Here's the javadoc which is not at all confusing:

"
Objects that are bound to a session may listen to container events notifying them that sessions will be passivated and that session will be activated. A container that migrates session between VMs or persists sessions is required to notify all attributes bound to sessions implementing HttpSessionActivationListener.
"

So, I can't just create a class that implements HttpSessionActivationListener and configure it using <listener> in web.xml.

I have to actually *put an object in the session* which implements that interface in order to get notifications. That means for my original goal, I need to:

1. Implement HttpSessionActivationListener

2. Implement HttpSessionListener to insert an object from (1) into each session, so my code can be notified

3. Implement Serializable, so the object-in-the-session doesn't prevent the session from being saved/loaded from secondary storage

None of these interfaces are necessary:

ServletContextListener
HttpSessionIdListener (at least for my use case)
HttpSessionBindingListener

So, really, the only thing I was missing from my initial implementation was "don't forget to put a copy of the session-manager-thing into each session's attributes".

I also need to ensure that only a single session "manager" object is created and used for all these operations. Without care, this could happen:

1. Application starts, and I have no sessions and a single instance of my session manager 2. Sessions are created; the one-instance of the session manager is stored in each session
3. The application stops, serializing all sessions to the disk
4. Upon de-serialization, new objects are created in-memory, though Java serialization ought to ensure that they are all the same instance linked through all sessions 5. ... but Tomcat has already created a new instance of this class to be its listener for the application.

So to get this working, I've had to implement a semi-singleton where the constructor is allowed, but everything really consults a central singleton storage mechanism regardless of which object instance is used for what purpose.

-chris

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscr...@tomcat.apache.org
For additional commands, e-mail: users-h...@tomcat.apache.org

Reply via email to