This is an automated email from the ASF dual-hosted git repository.

henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git


The following commit(s) were added to refs/heads/master by this push:
     new 09f222a2 JEXL : fix test ensuring JEXL holds no strong reference to 
classes
09f222a2 is described below

commit 09f222a29da04776e526b558c6221fefce57a84b
Author: Henrib <[email protected]>
AuthorDate: Fri Oct 16 10:29:42 2026 +0200

    JEXL : fix test ensuring JEXL holds no strong reference to classes
    
    testMany checks that, once scripts have used a class, JEXL does not hold a
    strong reference to it, directly or transitively, that would prevent the
    class from being unloaded.
    
    The test kept a SoftReference on a generated instance, which kept its class
    reachable until the GC cleared soft references. Whether that happened
    depended on heap size and GC algorithm, not on the JDK version: with a large
    heap, or with ZGC/Shenandoah, no class was ever unloaded. Since a failure 
only
    logged a warning, the test could not detect a leak.
    
    - keep a WeakReference instead of a SoftReference on the generated instance;
      keep the BigObject loop to add memory pressure as a GC hint
    - drain the reference queue correctly; the poll()/remove(1) pair dropped
      every other reference and could miss the class reference
    - actually run pass 1, which reloads classes 0..gced through fresh class
      loaders; the loop used to exit as soon as a class was GCed
    - assert that a generated class was GCed instead of only logging a warning
    - remove the Java-version assumption that skipped the test
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../org/apache/commons/jexl3/ClassCreatorTest.java | 38 +++++++++-------------
 1 file changed, 16 insertions(+), 22 deletions(-)

diff --git a/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java 
b/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java
index 7c768c82..7a16104d 100644
--- a/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java
+++ b/src/test/java/org/apache/commons/jexl3/ClassCreatorTest.java
@@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNotSame;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.File;
 import java.lang.ref.Reference;
@@ -36,7 +37,6 @@ import org.apache.commons.lang3.SystemProperties;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.Assumptions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -316,8 +316,6 @@ class ClassCreatorTest extends JexlTestCase {
 
     @Test
     void testMany() throws Exception {
-        // FIXME: java 28 has a new GC that seems to prevent classes from 
being GCed, so this test fails
-        Assumptions.assumeTrue(featureVersion() < 28, "testMany is known to 
fail on Java 28+");
         // abort test if class creator cannot run
         if (!ClassCreator.canRun) {
             return;
@@ -336,17 +334,16 @@ class ClassCreatorTest extends JexlTestCase {
         options.setSilent(true);
 
         final ClassCreator cctor = new ClassCreator(jexl, base);
-        for (int i = 0; i < LOOPS && gced < 0; ++i) {
+        // pass 0 creates classes until one is GCed (at index gced),
+        // pass 1 reloads classes 0..gced through fresh class loaders
+        for (int i = 0; pass == 0 ? i < LOOPS && gced < 0 : i <= gced; ++i) {
             cctor.setSeed(i);
             Class<?> clazz;
             if (pass == 0) {
                 clazz = cctor.createClass();
             } else {
                 clazz = cctor.getClassInstance();
-                if (clazz == null) {
-                    assertEquals(i, gced);
-                    break;
-                }
+                assertNotNull(clazz, "class " + i + " could not be reloaded");
             }
             // this code verifies the assumption that holding a strong 
reference to a method prevents
             // its owning class from being GCed
@@ -374,8 +371,9 @@ class ClassCreatorTest extends JexlTestCase {
             if (pass == 0) {
                 // add a weak reference on the class
                 stuff.add(new ClassReference(clazz, queue));
-                // add a soft reference on an instance
-                stuff.add(new 
InstanceReference(clazz.getConstructor().newInstance(), queue));
+                // add a weak reference on an instance; a soft one would keep 
the class reachable
+                // until the GC decides to clear soft references, which 
depends on heap size and GC flavor
+                stuff.add(new 
WeakReference<>(clazz.getConstructor().newInstance(), queue));
 
                 // attempt to force GC:
                 // while we still have a MB free, create & store big objects
@@ -386,26 +384,22 @@ class ClassCreatorTest extends JexlTestCase {
                 // hint it...
                 System.gc();
                 // let's see if some weak refs got collected
-                boolean qr = false;
-                while (queue.poll() != null) {
-                    final Reference<?> ref = queue.remove(1);
+                Reference<?> ref;
+                while ((ref = queue.remove(100)) != null) {
                     if (ref instanceof ClassReference) {
                         gced = i;
-                        qr = true;
                     }
                 }
-                if (qr) {
-                    //logger.warn("may have GCed class around " + i);
+                if (gced >= 0) {
+                    // restart from 0 on pass 1 (after ++i)
                     pass = 1;
-                    i = 0;
+                    i = -1;
                 }
             }
         }
-
-        if (gced < 0) {
-            logger.warn("unable to force GC");
-            //assertTrue(gced > 0);
-        }
+        // no class got unloaded means something holds a strong reference to 
them
+        assertTrue(gced >= 0, "unable to GC any generated class");
+        assertEquals(1, pass);
     }
 
     @Test

Reply via email to