This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 11.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit f5bfb5096ff1c5d39e62eec9d9ffebef50489e30 Author: opencode <[email protected]> AuthorDate: Wed Sep 30 23:04:04 2026 +0200 Capture and send the final session delta in DeltaSession.expire() before acquiring the session monitor to avoid a lock order inversion against the delta lock --- .../apache/catalina/ha/session/DeltaSession.java | 29 ++++++++++++++-------- webapps/docs/changelog.xml | 8 ++++++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/java/org/apache/catalina/ha/session/DeltaSession.java b/java/org/apache/catalina/ha/session/DeltaSession.java index 04140c487b..814298a07c 100644 --- a/java/org/apache/catalina/ha/session/DeltaSession.java +++ b/java/org/apache/catalina/ha/session/DeltaSession.java @@ -456,6 +456,25 @@ public class DeltaSession extends StandardSession implements Externalizable, Clu return; } + /* + * Obtaining and sending the final delta below takes the delta lock. That must not happen while the session + * monitor is held: code that runs while the delta lock is held, in particular the listener notifications + * triggered by StandardSession's attribute methods, may enter methods that take the session monitor, and the + * two orders would deadlock the two threads. The delta is therefore captured and sent before synchronizing. + * Concurrent expirations of the same session may then duplicate this message, which receivers handle + * harmlessly (a delta for an unknown session is ignored). The expired notification below remains under the + * monitor so it is sent only once. + */ + String expiredId = getIdInternal(); + + if (notifyCluster && expiredId != null && manager instanceof DeltaManager dmanager) { + CatalinaCluster cluster = dmanager.getCluster(); + ClusterMessage msg = dmanager.requestCompleted(expiredId, true); + if (msg != null) { + cluster.send(msg); + } + } + synchronized (this) { // Check again, now we are inside the sync so this code only runs once // Double check locking - isValid needs to be volatile @@ -467,16 +486,6 @@ public class DeltaSession extends StandardSession implements Externalizable, Clu return; } - String expiredId = getIdInternal(); - - if (notifyCluster && expiredId != null && manager instanceof DeltaManager dmanager) { - CatalinaCluster cluster = dmanager.getCluster(); - ClusterMessage msg = dmanager.requestCompleted(expiredId, true); - if (msg != null) { - cluster.send(msg); - } - } - super.expire(notify); if (notifyCluster) { diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index c0a80b5208..ad4888556e 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -170,6 +170,14 @@ </subsection> <subsection name="Cluster"> <changelog> + <fix> + Fix a possible deadlock in <code>DeltaSession.expire()</code>: the + final session delta is now captured and sent to the cluster before the + session monitor is acquired, because obtaining the delta takes the + delta lock and code that runs while the delta lock is held, such as + session attribute listener notifications, may in turn wait for the + session monitor. (remm) + </fix> <fix> Move context attributes that were set while the web application was still starting into the replicated attribute map when it is installed --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
