Search before asking
Java Version
Temurin 21.0.11 (console), built with Microsoft OpenJDK 11.0.28
Scala Version
2.12.x
StreamPark Version
3.0.0-SNAPSHOT (dev branch, commit 9ddda84c9)
Flink Version
1.20.4 and 2.2.1 (official binary distributions, standalone/remote cluster)
Deploy mode
remote
What happened
ClassLoaderUtils.runAsClassLoader(target, supplier) is meant to run a block under a given classloader and then put the calling thread back the way it found it. It does not: the finally restores ORIGINAL_CLASS_LOADER, a static final field initialised to Thread.currentThread().getContextClassLoader() at class-initialisation time — i.e. the context classloader of whichever thread first touched this class.
private static final ClassLoader ORIGINAL_CLASS_LOADER = Thread.currentThread().getContextClassLoader();
public static <R> R runAsClassLoader(ClassLoader targetClassLoader, Supplier<R> supplier) {
try {
Thread.currentThread().setContextClassLoader(targetClassLoader);
return supplier.get();
} finally {
Thread.currentThread().setContextClassLoader(ORIGINAL_CLASS_LOADER); // <-- not the caller's
}
}
The method is called from shared thread-pool threads, so for any caller whose context classloader was not that same value on entry, the "restore" silently installs a different classloader on that thread and leaves it there. Nothing fails at the call site; the damage lands on whatever runs on that pooled thread next.
The correct behaviour is to capture the calling thread's own value on entry and restore that. ORIGINAL_CLASS_LOADER is still needed by cloneClassLoader() and stays.
This is not a regression from the recent Scala-to-Java migration — git log -p shows the Scala version had the same shape.
Are you willing to submit PR?
Code of Conduct
Search before asking
ClassLoaderUtils/runAsClassLoader.Java Version
Temurin 21.0.11 (console), built with Microsoft OpenJDK 11.0.28
Scala Version
2.12.x
StreamPark Version
3.0.0-SNAPSHOT (
devbranch, commit9ddda84c9)Flink Version
1.20.4 and 2.2.1 (official binary distributions, standalone/remote cluster)
Deploy mode
remote
What happened
ClassLoaderUtils.runAsClassLoader(target, supplier)is meant to run a block under a given classloader and then put the calling thread back the way it found it. It does not: thefinallyrestoresORIGINAL_CLASS_LOADER, astatic finalfield initialised toThread.currentThread().getContextClassLoader()at class-initialisation time — i.e. the context classloader of whichever thread first touched this class.The method is called from shared thread-pool threads, so for any caller whose context classloader was not that same value on entry, the "restore" silently installs a different classloader on that thread and leaves it there. Nothing fails at the call site; the damage lands on whatever runs on that pooled thread next.
The correct behaviour is to capture the calling thread's own value on entry and restore that.
ORIGINAL_CLASS_LOADERis still needed bycloneClassLoader()and stays.This is not a regression from the recent Scala-to-Java migration —
git log -pshows the Scala version had the same shape.Are you willing to submit PR?
Code of Conduct