|
82 | 82 | import java.io.IOException; |
83 | 83 | import java.time.Duration; |
84 | 84 | import java.util.ArrayList; |
| 85 | +import java.util.Arrays; |
85 | 86 | import java.util.Collections; |
86 | 87 | import java.util.List; |
87 | 88 | import java.util.Map; |
|
133 | 134 | import java.util.Collection; |
134 | 135 | import java.util.Date; |
135 | 136 | import java.util.HashSet; |
136 | | -import java.util.Iterator; |
137 | 137 | import java.util.LinkedHashMap; |
138 | 138 | import java.util.Set; |
139 | 139 | import java.util.TreeSet; |
140 | 140 | import java.util.concurrent.CompletableFuture; |
141 | 141 | import java.util.concurrent.RejectedExecutionException; |
142 | 142 | import java.util.concurrent.TimeUnit; |
143 | 143 | import java.util.concurrent.atomic.LongAdder; |
| 144 | +import java.util.function.Predicate; |
144 | 145 | import java.util.stream.Collectors; |
145 | 146 | import edu.umd.cs.findbugs.annotations.CheckForNull; |
146 | 147 | import edu.umd.cs.findbugs.annotations.NonNull; |
@@ -1448,20 +1449,23 @@ private static void cleanUpLoader(ClassLoader loader, Set<ClassLoader> encounter |
1448 | 1449 | cleanUpLoader(loader.getParent(), encounteredLoaders, encounteredClasses); |
1449 | 1450 | LOGGER.finer(() -> "found " + loader); |
1450 | 1451 | SerializableClassRegistry.getInstance().release(loader); |
1451 | | - cleanUpGlobalClassValue(loader); |
1452 | 1452 | GroovyClassLoader gcl = (GroovyClassLoader) loader; |
1453 | | - for (Class<?> clazz : gcl.getLoadedClasses()) { |
| 1453 | + Set<Class<?>> loadedClasses = new HashSet<>(Arrays.asList((Class<?>[]) gcl.getLoadedClasses())); |
| 1454 | + // GroovyClassLoader.getLoadedClasses() only returns Groovy classes, not Java classes loaded when using `@Grab` |
| 1455 | + cleanUpGlobalClassValue(loader, loadedClasses); |
| 1456 | + cleanUpClassHelperCache(loader, loadedClasses); |
| 1457 | + for (Class<?> clazz : loadedClasses) { |
1454 | 1458 | if (encounteredClasses.add(clazz)) { |
1455 | 1459 | LOGGER.finer(() -> "found " + clazz.getName()); |
| 1460 | + // TODO: Do we also need to do a reverse lookup on the Introspector caches in case they have unique entries? |
1456 | 1461 | Introspector.flushFromCaches(clazz); |
1457 | | - cleanUpClassHelperCache(clazz); |
1458 | 1462 | cleanUpLoader(clazz.getClassLoader(), encounteredLoaders, encounteredClasses); |
1459 | 1463 | } |
1460 | 1464 | } |
1461 | 1465 | gcl.clearCache(); |
1462 | 1466 | } |
1463 | 1467 |
|
1464 | | - private static void cleanUpGlobalClassValue(@NonNull ClassLoader loader) throws Exception { |
| 1468 | + private static void cleanUpGlobalClassValue(@NonNull ClassLoader loader, Set<Class<?>> loadedClasses) throws Exception { |
1465 | 1469 | Class<?> classInfoC = Class.forName("org.codehaus.groovy.reflection.ClassInfo"); |
1466 | 1470 | // TODO switch to MethodHandle for speed |
1467 | 1471 | Field globalClassValueF = classInfoC.getDeclaredField("globalClassValue"); |
@@ -1491,31 +1495,47 @@ private static void cleanUpGlobalClassValue(@NonNull ClassLoader loader) throws |
1491 | 1495 | } |
1492 | 1496 | } |
1493 | 1497 | } |
1494 | | - Iterator<Class<?>> it = toRemove.iterator(); |
1495 | | - while (it.hasNext()) { |
1496 | | - Class<?> klazz = it.next(); |
1497 | | - ClassLoader encounteredLoader = klazz.getClassLoader(); |
1498 | | - if (encounteredLoader != loader) { |
1499 | | - it.remove(); |
1500 | | - if (LOGGER.isLoggable(Level.FINEST)) { |
1501 | | - LOGGER.finest(() -> "ignoring " + klazz + " with loader " + encounteredLoader); |
1502 | | - } |
1503 | | - } |
1504 | | - } |
| 1498 | + toRemove.removeIf(isClassFromOtherClassLoader(loader)); |
1505 | 1499 | LOGGER.fine(() -> "cleaning up " + toRemove + " associated with " + loader); |
1506 | 1500 | for (Class<?> klazz : toRemove) { |
| 1501 | + loadedClasses.add(klazz); |
1507 | 1502 | removeM.invoke(map, klazz); |
1508 | 1503 | } |
1509 | 1504 | } |
1510 | 1505 |
|
1511 | | - private static void cleanUpClassHelperCache(@NonNull Class<?> clazz) throws Exception { |
| 1506 | + private static void cleanUpClassHelperCache(@NonNull ClassLoader loader, Set<Class<?>> loadedClasses) throws Exception { |
1512 | 1507 | Field classCacheF = Class.forName("org.codehaus.groovy.ast.ClassHelper$ClassHelperCache").getDeclaredField("classCache"); |
1513 | 1508 | classCacheF.setAccessible(true); |
1514 | 1509 | Object classCache = classCacheF.get(null); |
1515 | | - if (LOGGER.isLoggable(Level.FINER)) { |
1516 | | - LOGGER.log(Level.FINER, "cleaning up {0} from ClassHelperCache? {1}", new Object[] {clazz.getName(), classCache.getClass().getMethod("get", Object.class).invoke(classCache, clazz) != null}); |
| 1510 | + Class<?> classCacheC = classCache.getClass(); |
| 1511 | + Collection entries = (Collection) classCacheC.getMethod("values").invoke(classCache); |
| 1512 | + Class<?> managedRefC = Class.forName("org.codehaus.groovy.util.ManagedReference"); |
| 1513 | + var getRefM = managedRefC.getMethod("get"); |
| 1514 | + List<Class<?>> toRemove = new ArrayList<>(); // not sure if it is safe against ConcurrentModificationException or not |
| 1515 | + for (Object entry : entries) { |
| 1516 | + Class<?> clazz = (Class<?>) getRefM.invoke(entry); |
| 1517 | + if (clazz != null) { |
| 1518 | + toRemove.add(clazz); |
| 1519 | + } |
1517 | 1520 | } |
1518 | | - classCache.getClass().getMethod("remove", Object.class).invoke(classCache, clazz); |
| 1521 | + toRemove.removeIf(isClassFromOtherClassLoader(loader)); |
| 1522 | + LOGGER.fine(() -> "cleaning up " + toRemove + " associated with " + loader); |
| 1523 | + Method removeM = classCache.getClass().getMethod("remove", Object.class); |
| 1524 | + for (Class<?> klazz : toRemove) { |
| 1525 | + loadedClasses.add(klazz); |
| 1526 | + removeM.invoke(classCache, klazz); |
| 1527 | + } |
| 1528 | + } |
| 1529 | + |
| 1530 | + private static Predicate<Class<?>> isClassFromOtherClassLoader(ClassLoader loader) { |
| 1531 | + return klazz -> { |
| 1532 | + ClassLoader encounteredLoader = klazz.getClassLoader(); |
| 1533 | + var irrelevant = encounteredLoader != loader; |
| 1534 | + if (irrelevant) { |
| 1535 | + LOGGER.finest(() -> "ignoring " + klazz + " with loader " + encounteredLoader); |
| 1536 | + } |
| 1537 | + return irrelevant; |
| 1538 | + }; |
1519 | 1539 | } |
1520 | 1540 |
|
1521 | 1541 | synchronized @CheckForNull FlowHead getFirstHead() { |
|
0 commit comments