You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by re...@apache.org on 2026/10/12 13:58:44 UTC
(tomcat) 13/22: Interrupt a stuck thread only once per request
This is an automated email from the ASF dual-hosted git repository.
rmaucher pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 7cfeca8dffc4a15be403edd204e1bcd21e45b332
Author: opencode <re...@apache.org>
AuthorDate: Mon Oct 12 13:26:42 2026 +0200
Interrupt a stuck thread only once per request
A thread that remained stuck despite having been interrupted, because
its work was not interruptible or the application ignored interrupts,
was interrupted again and WARN logged with a full stack trace on every
background process cycle for the remaining lifetime of the request.
Record that the thread of a monitored request has been interrupted and
skip subsequent interruption attempts. The thread is now interrupted,
and logged, once per request. Add a test using a servlet that performs
non-interruptible work and counts every interrupt delivered to it.
---
.../catalina/valves/StuckThreadDetectionValve.java | 12 +++-
.../valves/TestStuckThreadDetectionValve.java | 73 ++++++++++++++++++++++
2 files changed, 83 insertions(+), 2 deletions(-)
diff --git a/java/org/apache/catalina/valves/StuckThreadDetectionValve.java b/java/org/apache/catalina/valves/StuckThreadDetectionValve.java
index df1c1ace50..51a58c8931 100644
--- a/java/org/apache/catalina/valves/StuckThreadDetectionValve.java
+++ b/java/org/apache/catalina/valves/StuckThreadDetectionValve.java
@@ -300,8 +300,9 @@ public class StuckThreadDetectionValve extends ValveBase {
*/
private final Semaphore interruptionSemaphore;
/**
- * Set to true after the thread is interrupted. No need to make it volatile since it is accessed right after
- * acquiring the semaphore.
+ * Set to true after the thread is interrupted. Also checked to avoid interrupting, and logging, a thread that
+ * remains stuck on every background process cycle. No need to make it volatile since it is read and written
+ * while holding the interruption semaphore, and read right after acquiring it.
*/
private boolean interrupted;
@@ -371,6 +372,13 @@ public class StuckThreadDetectionValve extends ValveBase {
return false;
}
try {
+ if (this.interrupted) {
+ // The thread has already been interrupted for this request.
+ // A thread that is still stuck despite the interrupt would
+ // otherwise be interrupted, and logged, again on every
+ // background process cycle.
+ return false;
+ }
if (log.isWarnEnabled()) {
String msg = sm.getString("stuckThreadDetectionValve.notifyStuckThreadInterrupted",
this.getThread().getName(), Long.valueOf(getActiveTimeInMillis()), this.getStartTime(),
diff --git a/test/org/apache/catalina/valves/TestStuckThreadDetectionValve.java b/test/org/apache/catalina/valves/TestStuckThreadDetectionValve.java
index 039870e9a2..95b493ea54 100644
--- a/test/org/apache/catalina/valves/TestStuckThreadDetectionValve.java
+++ b/test/org/apache/catalina/valves/TestStuckThreadDetectionValve.java
@@ -19,6 +19,7 @@ package org.apache.catalina.valves;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@@ -133,6 +134,78 @@ public class TestStuckThreadDetectionValve extends TomcatBaseTest {
Assert.assertTrue(result.toString().startsWith("OK"));
}
+ @Test
+ public void testInterruptStuckThreadOnlyOnce() throws Exception {
+ // A servlet whose work is not interruptible: it stays busy even after
+ // being interrupted and records every interrupt that is delivered
+ UninterruptibleStickingServlet stickingServlet =
+ new UninterruptibleStickingServlet(TimeUnit.SECONDS.toMillis(8L));
+ Wrapper servlet = Tomcat.addServlet(context, "myservlet", stickingServlet);
+ servlet.addMapping("/myservlet");
+
+ StuckThreadDetectionValve valve = new StuckThreadDetectionValve();
+ valve.setThreshold(2);
+ valve.setInterruptThreadThreshold(3);
+ context.addValve(valve);
+ context.setBackgroundProcessorDelay(1);
+ tomcat.start();
+
+ final ByteChunk result = new ByteChunk();
+ Thread asyncThread = new Thread() {
+ @Override
+ public void run() {
+ try {
+ getUrl("http://localhost:" + getPort() + "/myservlet", result, null);
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+ }
+
+ };
+ asyncThread.start();
+ asyncThread.join(20000);
+ // check that we did not reach the join timeout
+ Assert.assertFalse(asyncThread.isAlive());
+
+ // The thread should have been interrupted, but only once. A thread
+ // that remains stuck despite the interrupt must not be interrupted,
+ // and logged, again on every background process cycle.
+ Assert.assertEquals(1, stickingServlet.getInterruptCount());
+ Assert.assertTrue(result.toString().startsWith("OK"));
+ }
+
+ private static class UninterruptibleStickingServlet extends HttpServlet {
+
+ private static final long serialVersionUID = 1L;
+ private final long duration;
+ private final AtomicInteger interruptCount = new AtomicInteger();
+
+ UninterruptibleStickingServlet(long duration) {
+ this.duration = duration;
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ // Busy wait, like work that ignores the interrupt. Each delivered
+ // interrupt is observed once and cleared, so a new interrupt of
+ // the same request is counted separately.
+ long end = System.currentTimeMillis() + duration;
+ while (System.currentTimeMillis() < end) {
+ if (Thread.interrupted()) {
+ interruptCount.incrementAndGet();
+ }
+ }
+ // Do not hand an interrupt flag back to the container
+ Thread.interrupted();
+ resp.setContentType("text/plain");
+ resp.getWriter().println("OK");
+ }
+
+ public int getInterruptCount() {
+ return interruptCount.get();
+ }
+ }
+
private static class StickingServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org