ClassNotFoundException when deserializing custom object with FileStore for PersistentManagerBase.processExpires

2024-12-03 Thread Pascal Rigaux

Hi,

I have bug https://bz.apache.org/bugzilla/show_bug.cgi?id=48007 on recent 
tomcats.

To reproduce one must:
- use PersistentManager in global context.xml
- have an object in session only available through a webapp class loader
- wait for manager backgroundProcess to call PersistentManagerBase.processExpires 
> Storebase.processExpires
  (by default, 6 * 10 seconds)

=> it fails to unserialize, and removes the session from FileStore.


My technical understanding of the issue:

getObjectInputStream() is using Thread.currentThread().getContextClassLoader()
which is set in FileStore.load() using current Context classLoader

This is ok when called by manager.findSession(...) , but 
Storebase.processExpires() is using current context

=> it is ok when PersistentManager is in the application specific
context.xml
=> it is wrong when PersistentManager is in global context.xml

One can workaround the issue by moving  from global 
context.xml to webapp context.xml ,
as explained here : 
https://dev.wicket.apache.narkive.com/KXmPFrnQ/problems-with-tomcat-session-persistence-classnotfoundexception-of-secondlevelcachesessionstore

If you use PersistentValve, another workaround is to use  + an external cron.


Stacktrace with tomcat 9:

SEVERE [Catalina-utility-3] 
org.apache.catalina.session.StoreBase.processExpires Error processing session 
expiration for key [xxx]
java.lang.ClassNotFoundException: 
org.springframework.security.core.context.SecurityContextImpl
at 
org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1339)
at 
org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1148)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Unknown Source)
at 
org.apache.catalina.util.CustomObjectInputStream.resolveClass(CustomObjectInputStream.java:149)
at java.base/java.io.ObjectInputStream.readNonProxyDesc(Unknown 
Source)
at java.base/java.io.ObjectInputStream.readClassDesc(Unknown 
Source)
at 
java.base/java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.base/java.io.ObjectInputStream.readObject0(Unknown 
Source)
at java.base/java.io.ObjectInputStream.readObject(Unknown 
Source)
at java.base/java.io.ObjectInputStream.readObject(Unknown 
Source)
at 
org.apache.catalina.session.StandardSession.doReadObject(StandardSession.java:1268)
at 
org.apache.catalina.session.StandardSession.readObjectData(StandardSession.java:846)
at 
org.apache.catalina.session.FileStore.load(FileStore.java:203)
at 
org.apache.catalina.session.StoreBase.processExpires(StoreBase.java:138)
at 
org.apache.catalina.session.PersistentManagerBase.processExpires(PersistentManagerBase.java:409)
at 
org.apache.catalina.session.ManagerBase.backgroundProcess(ManagerBase.java:595)
at 
org.apache.catalina.core.StandardContext.backgroundProcess(StandardContext.java:4823)
..

It would be nice in my use-cases to be able to configure globally 
PersistentManager.
It would be enough for us to have an option to disable 
Storebase.processExpires(), but it may be somewhat ugly...

cu
Pascal Rigaux.

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



Re: ClassNotFoundException when deserializing custom object with FileStore for PersistentManagerBase.processExpires

2024-12-05 Thread Pascal Rigaux

On 04/12/2024 18:09, Mark Thomas wrote:

The Manager/Store component can't easily determine if it is being configured 
from the global, host or context level context.xml file.

The root cause here is configuration error - configuring multiple web 
applications to use the same file for session persistence.


Thanks for the explantation!


Suggestions for StandardManager.file() :


- allowing a directory as absolute pathname:

  if (file.isAbsolute()) {
if (file.isDirectory()) {
  var contextName = getContext().getBaseName();
  return new File(file, "SESSIONS-" + contextName + ".ser");


- a quite ugly code to detect bad usage:

static Map pathname2contextName = new HashMap<>();

...

  if (file.isAbsolute()) {
var contextName = getContext().getBaseName();
var prev = pathname2contextName.get(pathname);
if (prev != null && !prev.equals(contextName)) {
  log.error("You can not share same absolute \"pathname\" for multiple s (found same 
\"pathname\" for " + contextName + " and " + prev + ")");
}
pathname2contextName.put(pathname, contextName);


cu


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



Re: ClassNotFoundException when deserializing custom object with FileStore for PersistentManagerBase.processExpires

2024-12-03 Thread Pascal Rigaux

Oops, I missed something :

When you use  or 
the behaviour is different if the pathname/directory is absolute or relative.

If it is relative, it is stored relative to each applications temporary work 
directory, and handled with each application classLoader, and it works 
perfectly.

Maybe this should be mentionned more clearly in the documentation? Maybe 
display a warning log for absolute pathname/directory in global context.xml?

cu,
Pascal R.

On 03/12/2024 16:36, Pascal Rigaux wrote:

Hi,

I have bug https://bz.apache.org/bugzilla/show_bug.cgi?id=48007 on recent 
tomcats.

To reproduce one must:
- use PersistentManager in global context.xml
- have an object in session only available through a webapp class loader
- wait for manager backgroundProcess to call PersistentManagerBase.processExpires 
> Storebase.processExpires
   (by default, 6 * 10 seconds)

=> it fails to unserialize, and removes the session from FileStore.


My technical understanding of the issue:

getObjectInputStream() is using Thread.currentThread().getContextClassLoader()
which is set in FileStore.load() using current Context classLoader

This is ok when called by manager.findSession(...) , but 
Storebase.processExpires() is using current context

=> it is ok when PersistentManager is in the application specific
context.xml
=> it is wrong when PersistentManager is in global context.xml

One can workaround the issue by moving  from global 
context.xml to webapp context.xml ,
as explained here : 
https://dev.wicket.apache.narkive.com/KXmPFrnQ/problems-with-tomcat-session-persistence-classnotfoundexception-of-secondlevelcachesessionstore

If you use PersistentValve, another workaround is to use  + an external cron.


Stacktrace with tomcat 9:

SEVERE [Catalina-utility-3] 
org.apache.catalina.session.StoreBase.processExpires Error processing session 
expiration for key [xxx]
     java.lang.ClassNotFoundException: 
org.springframework.security.core.context.SecurityContextImpl
     at 
org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1339)
     at 
org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1148)
     at java.base/java.lang.Class.forName0(Native Method)
     at java.base/java.lang.Class.forName(Unknown Source)
     at 
org.apache.catalina.util.CustomObjectInputStream.resolveClass(CustomObjectInputStream.java:149)
     at 
java.base/java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
     at java.base/java.io.ObjectInputStream.readClassDesc(Unknown 
Source)
     at 
java.base/java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
     at java.base/java.io.ObjectInputStream.readObject0(Unknown 
Source)
     at java.base/java.io.ObjectInputStream.readObject(Unknown 
Source)
     at java.base/java.io.ObjectInputStream.readObject(Unknown 
Source)
     at 
org.apache.catalina.session.StandardSession.doReadObject(StandardSession.java:1268)
     at 
org.apache.catalina.session.StandardSession.readObjectData(StandardSession.java:846)
     at 
org.apache.catalina.session.FileStore.load(FileStore.java:203)
     at 
org.apache.catalina.session.StoreBase.processExpires(StoreBase.java:138)
     at 
org.apache.catalina.session.PersistentManagerBase.processExpires(PersistentManagerBase.java:409)
     at 
org.apache.catalina.session.ManagerBase.backgroundProcess(ManagerBase.java:595)
     at 
org.apache.catalina.core.StandardContext.backgroundProcess(StandardContext.java:4823)
     ..

It would be nice in my use-cases to be able to configure globally 
PersistentManager.
It would be enough for us to have an option to disable 
Storebase.processExpires(), but it may be somewhat ugly...

cu
Pascal Rigaux.


--
Pascal Rigaux

Expert en développement et déploiement d'applications
DSIUN-PAS (Pôle Applications et Services numériques)
Université Paris 1 Panthéon-Sorbonne  -  Centre Pierre Mendès France (PMF)
Coordonnées : https://annuaire.univ-paris1.fr/Pascal.Rigaux@


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



session Manager "maxActiveSessions" alternative behavior : allow new session but expire old session

2025-01-08 Thread Pascal Rigaux

Hi,

On some applications we have:
- quite low number of users most of the time
- high number of users twice a year

These applications store quite a lot of information in session.
To cope with the surge of users, we would need to:
- either increase mx (Java max memory)
- or use short session timeout (lifetime)
- or use horizontal scaling (k8s... but we currently have no such solutions)

The best solution for us would be a "maxActiveSessions" setting which would set 
a high bound to the number of sessions,
but compared to current implementation, it would allow new sessions and expire 
old sessions.

We mostly achieved this with a cron (1) calling a script (2): it uses 
tomcat-manager to force a shorter lifetime when there is a lot of sessions.

Since it would be nicer to have this behavior directly in tomcat, I did it 
using a small class extending StandardManager (2).
I wonder if this could be useful to other users?...

cu.


(1) */10 * * * * ~/tomcat/expire-if-too-many-sessions student-info 4000 8
(2) script "expire-if-too-many-sessions":
--
webapp=$1
max_sessions=$2
idle=$3

nb_sessions=`curl -s --user "$user" "$url/manager/text/list" | grep "$webapp" | 
awk -F: '{print $3}'`

if [ $nb_sessions -gt $max_sessions ]; then
echo "too many sessions ($nb_sessions > $max_sessions). telling tomcat to expire 
$webapp sessions inactive more than $idle"
out=`curl -s --user "$user" 
"$url/manager/text/expire?path=/$webapp&idle=$idle"`
if echo $out | grep -q '^OK'; then
:
else
echo "error expiring: $out"
fi
fi
-


(3)
-
protected int maxActiveSessionsGoal = -1;

/**
 * If you have too many sessions, you may memory overflow.
 * This setting will expire old sessions to keep sessions memory usage low.
 * -1 is no limit
 */
public void setMaxActiveSessionsGoal(int max) {
maxActiveSessionsGoal = max;
}


public void processExpires() {
super.processExpires();
if (maxActiveSessionsGoal >= 0) {
var nb = getActiveSessions();
if (nb > maxActiveSessionsGoal) {
expireNbOldSessions(nb - maxActiveSessionsGoal);
}
}
}

private void expireNbOldSessions(int nbToRemove) {
var time = sessions.values().stream()
.mapToLong(Session::getLastAccessedTimeInternal).sorted()
// we want the nbToRemove-th element in the array
.skip(nbToRemove).findFirst()
.orElse(0);
if (time == 0) {
log.error("internal error expireNbOldSessions");
return;
}
log.info("To achieve maxActiveSessionsGoal (" + maxActiveSessionsGoal + ") for " + 
getContext().getBaseName() + ", we will expire sessions older than " + new Date(time) + " (" + nbToRemove + 
" sessions)");
for (Session session : findSessions()) {
if (session.getLastAccessedTimeInternal() < time) {
session.expire();
}
}
}
-

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



Re: session Manager "maxActiveSessions" alternative behavior : allow new session but expire old session

2025-01-09 Thread Pascal Rigaux

Hi,

On 08/01/2025 22:13, Christopher Schultz wrote:

[...]
It would allow anyone to force a logout of all current users at will just by 
making any request that causes an unauthenticated session to be created.

Instant DOS.


Note that current "maxActiveSessions" implementation also causes a DOS: if you 
can create many sessions, it will block new users.

I must look at the application unauthenticated sessions:
- if they are already big, there is already a DOS via OutOfMemory
- if there are small, they would need to be handled specifically, expiring them 
first

In any case, as you suggested, the application should not depend on such big 
sessions. That's the real solution to avoid any issues!

cu
Pascal Rigaux.


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