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 d0a8267a670cd9669f8aebe4d1edc8b8cfca3044
Author: opencode <[email protected]>
AuthorDate: Fri Oct 16 13:22:07 2026 +0200

    Ignore extra close() calls in ReferenceCountedJar
    
    An extra close() call (more closes than opens) drove the reference count
    below zero. Since the wrapped Jar is only closed when a balanced close
    returns the count to exactly zero, once the count went negative no
    subsequent open()/close() cycle would close the wrapped Jar again,
    leaking it and its file handle until the garbage collector ran the
    cleaner. Extra close() calls are now ignored, saturating the count at
    zero. Current callers all balance their references, so this hardens the
    public API rather than fixing an active leak.
---
 .../tomcat/util/scan/ReferenceCountedJar.java      |  6 +++++
 .../tomcat/util/scan/TestReferenceCountedJar.java  | 31 ++++++++++++++++++++++
 2 files changed, 37 insertions(+)

diff --git a/java/org/apache/tomcat/util/scan/ReferenceCountedJar.java 
b/java/org/apache/tomcat/util/scan/ReferenceCountedJar.java
index 83f3dc1bce..6330ae3c24 100644
--- a/java/org/apache/tomcat/util/scan/ReferenceCountedJar.java
+++ b/java/org/apache/tomcat/util/scan/ReferenceCountedJar.java
@@ -61,6 +61,12 @@ public class ReferenceCountedJar implements Jar {
 
     @Override
     public synchronized void close() {
+        if (referenceCount <= 0) {
+            // More closes than opens have been requested. Ignore the extra
+            // close rather than letting the count go negative, which would
+            // prevent the wrapped Jar from ever being closed again.
+            return;
+        }
         referenceCount--;
         if (referenceCount == 0) {
             wrappedJar.close();
diff --git a/test/org/apache/tomcat/util/scan/TestReferenceCountedJar.java 
b/test/org/apache/tomcat/util/scan/TestReferenceCountedJar.java
index 77a6c8a117..33775f1815 100644
--- a/test/org/apache/tomcat/util/scan/TestReferenceCountedJar.java
+++ b/test/org/apache/tomcat/util/scan/TestReferenceCountedJar.java
@@ -21,6 +21,7 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.reflect.Field;
 import java.net.URI;
 import java.nio.charset.StandardCharsets;
 import java.util.jar.Attributes;
@@ -179,4 +180,34 @@ public class TestReferenceCountedJar {
             Assert.assertEquals(CONTENT, readFully(is));
         }
     }
+
+
+    /*
+     * Extra close() calls (more closes than opens) must not drive the 
reference count below zero. If the count were to
+     * go negative, subsequent open()/close() cycles would never return it to 
zero and the wrapped Jar would never be
+     * closed again, leaking it.
+     */
+    @Test
+    public void testExtraCloseDoesNotGoNegative() throws Exception {
+        File file = createJar();
+        URI jarUri = URI.create("jar:" + file.toURI().toURL().toExternalForm() 
+ "!/");
+
+        ReferenceCountedJar jar = new ReferenceCountedJar(jarUri.toURL());
+        Field countField = 
ReferenceCountedJar.class.getDeclaredField("referenceCount");
+        countField.setAccessible(true);
+
+        jar.close();
+        Assert.assertEquals(0, countField.getInt(jar));
+
+        // Extra closes must be ignored rather than decrementing below zero.
+        jar.close();
+        jar.close();
+        Assert.assertEquals(0, countField.getInt(jar));
+
+        // A later open()/close() cycle must still be able to return to zero 
and release the wrapped Jar.
+        try (InputStream is = jar.getInputStream(ENTRY_NAME)) {
+            Assert.assertEquals(CONTENT, readFully(is));
+        }
+        Assert.assertEquals(0, countField.getInt(jar));
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to