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 4395e951b002067c377c3658fef4043bec2373eb
Author: opencode <[email protected]>
AuthorDate: Tue Oct 20 10:26:37 2026 +0200

    Release the log handlers of the given class loader in JULI
    
    LogFactory.release() never used its class loader argument. Since the
    JULI log manager resolves the configuration to reset through the
    context class loader of the current thread, the handlers of the
    stopping application were only closed when the thread happened to run
    with that class loader as the context one, and the handlers of
    whatever other class loader was current closed instead, leaking the
    open log files of the application until the VM exited.
    
    The release path of the container binds its thread to the class
    loader to stop beforehand, so there the argument and the context
    class loader already agree and the reset is a plain reset as before.
    In the other cases, the class loader to release is now made the
    context class loader for the duration of the reset and the previous
    one restored afterwards. The argument is documented as required and
    as briefly creating a missing configuration to release it, matching
    what the reset does internally.
---
 java/org/apache/juli/logging/LogFactory.java | 34 +++++++++++++++++++++-------
 1 file changed, 26 insertions(+), 8 deletions(-)

diff --git a/java/org/apache/juli/logging/LogFactory.java 
b/java/org/apache/juli/logging/LogFactory.java
index 4df17a1f0a..37a3fc4009 100644
--- a/java/org/apache/juli/logging/LogFactory.java
+++ b/java/org/apache/juli/logging/LogFactory.java
@@ -187,18 +187,36 @@ public class LogFactory {
 
 
     /**
-     * Reset the {@link java.util.logging.LogManager}, unless the standard 
<code>java.util.logging.LogManager</code>
-     * implementation is in use. The given class loader is not used: the JULI 
log manager associates its configuration
-     * with the current thread's context class loader.
+     * Release the log handlers that the JULI {@link 
java.util.logging.LogManager} has associated with the given class
+     * loader, so a stopped application no longer keeps its log files open. If 
the class loader has no logging
+     * configuration yet, one is briefly created for it and released again. 
This is a NO-OP when the standard
+     * <code>java.util.logging.LogManager</code> implementation is in use: 
that one has a single global configuration
+     * and resetting it would discard the logging of the whole JVM. The JULI 
log manager associates its configuration
+     * with the current thread's context class loader, so the given class 
loader is made current for the duration of
+     * the reset.
      *
-     * @param classLoader ClassLoader for which to release the LogFactory. Not 
used by this implementation.
+     * @param classLoader ClassLoader for which to release the LogFactory. 
Must not be null.
      */
     public static void release(ClassLoader classLoader) {
-        // JULI's log manager looks at the current classLoader so there is no
-        // need to use the passed in classLoader, the default implementation
-        // does not so call reset in that case will break things
+        // JULI's log manager looks at the current classLoader so the
+        // classLoader to release has to be the current one, the default
+        // implementation does not so calling reset in that case would reset
+        // the global configuration and break the logging of the whole JVM
         if 
(!LogManager.getLogManager().getClass().getName().equals("java.util.logging.LogManager"))
 {
-            LogManager.getLogManager().reset();
+            Thread currentThread = Thread.currentThread();
+            ClassLoader originalClassLoader = 
currentThread.getContextClassLoader();
+            if (originalClassLoader == classLoader) {
+                // The class loader to release is already the current one, as 
on the
+                // regular container stop path that binds its thread beforehand
+                LogManager.getLogManager().reset();
+            } else {
+                try {
+                    currentThread.setContextClassLoader(classLoader);
+                    LogManager.getLogManager().reset();
+                } finally {
+                    currentThread.setContextClassLoader(originalClassLoader);
+                }
+            }
         }
     }
 }


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

Reply via email to