This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 53911b4e7eab7d4391bca8f94861729a38d770db 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 7508d5ce26..490648d078 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 d59d471101..e7d453683a 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -401,6 +401,14 @@ <subsection name="Cluster"> <changelog> <!-- Entries for backport and removal before 12.0.0-M1 below this line --> + <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]
