-
Notifications
You must be signed in to change notification settings - Fork 195
[JENKINS-63766] Work around JDK-8231454 #543
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,6 +52,7 @@ | |
import hudson.model.Action; | ||
import hudson.model.Result; | ||
import hudson.util.Iterators; | ||
import io.jenkins.lib.versionnumber.JavaSpecificationVersion; | ||
import jenkins.model.CauseOfInterruption; | ||
import jenkins.model.Jenkins; | ||
import org.jboss.marshalling.Unmarshaller; | ||
|
@@ -1298,6 +1299,7 @@ private static void cleanUpLoader(ClassLoader loader, Set<ClassLoader> encounter | |
if (encounteredClasses.add(clazz)) { | ||
LOGGER.log(Level.FINER, "found {0}", clazz.getName()); | ||
Introspector.flushFromCaches(clazz); | ||
cleanUpClassInfoCache(clazz); | ||
cleanUpGlobalClassSet(clazz); | ||
cleanUpClassHelperCache(clazz); | ||
cleanUpObjectStreamClassCaches(clazz); | ||
|
@@ -1358,6 +1360,44 @@ private static void cleanUpGlobalClassValue(@NonNull ClassLoader loader) throws | |
} | ||
} | ||
|
||
private static void cleanUpClassInfoCache(Class<?> clazz) { | ||
JavaSpecificationVersion current = JavaSpecificationVersion.forCurrentJVM(); | ||
if (current.isNewerThan(new JavaSpecificationVersion("1.8")) | ||
&& current.isOlderThan(new JavaSpecificationVersion("16"))) { | ||
Comment on lines
+1365
to
+1366
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As mentioned in JENKINS-63766, this bug was introduced in Java 9 and was fixed in Java 16. The only version of Java in this range that we expect users to be using realistically is Java 11, but cover the whole range just to be safe. |
||
try { | ||
// TODO Work around JDK-8231454. | ||
Class<?> classInfoC = Class.forName("com.sun.beans.introspect.ClassInfo"); | ||
Field cacheF = classInfoC.getDeclaredField("CACHE"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As an aside, does anyone happen to know why this is not just using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought about this yesterday, too. Ask the OpenJDK people? |
||
try { | ||
cacheF.setAccessible(true); | ||
} catch (RuntimeException e) { // TOOD Java 9+ InaccessibleObjectException | ||
basil marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/* | ||
* Not running with "--add-opens java.desktop/com.sun.beans.introspect=ALL-UNNAMED". | ||
* Until core adds this to its --add-opens configuration, and until that core | ||
* change is widely adopted, avoid unnecessary log spam and return early. | ||
*/ | ||
if (LOGGER.isLoggable(Level.FINER)) { | ||
LOGGER.log(Level.FINER, "Failed to clean up " + clazz.getName() + " from ClassInfo#CACHE. A metaspace leak may have occurred.", e); | ||
} | ||
return; | ||
} | ||
Object cache = cacheF.get(null); | ||
Class<?> cacheC = Class.forName("com.sun.beans.util.Cache"); | ||
if (LOGGER.isLoggable(Level.FINER)) { | ||
LOGGER.log(Level.FINER, "Cleaning up " + clazz.getName() + " from ClassInfo#CACHE."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was tempted to print whether the class was already in the cache or not, as is done elsewhere in this file, but |
||
} | ||
Method removeM = cacheC.getMethod("remove", Object.class); | ||
removeM.invoke(cache, clazz); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} catch (ReflectiveOperationException e) { | ||
/* | ||
* Should never happen, but if it does, ensure the failure is isolated to this | ||
* method and does not prevent other cleanup logic from executing. | ||
*/ | ||
LOGGER.log(Level.WARNING, "Failed to clean up " + clazz.getName() + " from ClassInfo#CACHE. A metaspace leak may have occurred.", e); | ||
} | ||
} | ||
} | ||
|
||
private static void cleanUpGlobalClassSet(@NonNull Class<?> clazz) throws Exception { | ||
Class<?> classInfoC = Class.forName("org.codehaus.groovy.reflection.ClassInfo"); // or just ClassInfo.class, but unclear whether this will always be there | ||
Field globalClassSetF = classInfoC.getDeclaredField("globalClassSet"); | ||
|
@@ -1405,13 +1445,16 @@ private static void cleanUpObjectStreamClassCaches(@NonNull Class<?> clazz) thro | |
for (String cacheFName : new String[] {"localDescs", "reflectors"}) { | ||
Field cacheF = cachesC.getDeclaredField(cacheFName); | ||
cacheF.setAccessible(true); | ||
ConcurrentMap<Reference<Class<?>>, ?> cache = (ConcurrentMap) cacheF.get(null); | ||
Iterator<? extends Entry<Reference<Class<?>>, ?>> iterator = cache.entrySet().iterator(); | ||
while (iterator.hasNext()) { | ||
if (iterator.next().getKey().get() == clazz) { | ||
iterator.remove(); | ||
LOGGER.log(Level.FINER, "cleaning up {0} from ObjectStreamClass.Caches.{1}", new Object[] {clazz.getName(), cacheFName}); | ||
break; | ||
Object cache = cacheF.get(null); | ||
if (cache instanceof ConcurrentMap) { | ||
// Prior to JDK-8277072 | ||
Iterator<? extends Entry<Reference<Class<?>>, ?>> iterator = ((ConcurrentMap) cache).entrySet().iterator(); | ||
while (iterator.hasNext()) { | ||
if (iterator.next().getKey().get() == clazz) { | ||
iterator.remove(); | ||
LOGGER.log(Level.FINER, "cleaning up {0} from ObjectStreamClass.Caches.{1}", new Object[]{clazz.getName(), cacheFName}); | ||
break; | ||
} | ||
Comment on lines
+1448
to
+1457
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a separate fix for a related bug I noticed while testing under latest OpenJDK 11.0.16 (in particular, while testing with openjdk/jdk11u-dev#1103). I got the following new error:
Looking into this further, it appears the type of this field was changed in JDK-8277072, which was backported to OpenJDK 11.0.16, which I happened to be testing. Skipping over this logic got the error to go away, and I confirmed that the class was still unloaded when running with JDK-8277072 and without entering this |
||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any idea if the same issue applies to sandboxed non-Pipeline Groovy scripts as well? This code duplicates https://github.com/jenkinsci/script-security-plugin/blob/35f6a0b8207ed3a32a85f27c1312da6cd738eeaa/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java#L205-L214.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No idea.
jenkinsci/script-security-plugin#420