|
30 | 30 | import java.util.Arrays; |
31 | 31 | import java.util.Collections; |
32 | 32 | import java.util.concurrent.atomic.AtomicLong; |
| 33 | +import java.util.concurrent.atomic.AtomicReference; |
33 | 34 | import org.junit.jupiter.api.BeforeEach; |
34 | 35 | import org.junit.jupiter.api.Test; |
35 | 36 | import org.junit.jupiter.api.extension.ExtendWith; |
@@ -219,6 +220,59 @@ void invokeCallback_NoStorage() { |
219 | 220 | assertThat(counter.get()).isEqualTo(0); |
220 | 221 | } |
221 | 222 |
|
| 223 | + @Test |
| 224 | + void invokeCallback_RestoresContextClassLoader() { |
| 225 | + // Simulate the context class loader at registration time |
| 226 | + ClassLoader registrationClassLoader = new ClassLoader() {}; |
| 227 | + ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); |
| 228 | + |
| 229 | + Thread.currentThread().setContextClassLoader(registrationClassLoader); |
| 230 | + AtomicReference<ClassLoader> observedClassLoader = new AtomicReference<>(); |
| 231 | + Runnable callback = |
| 232 | + () -> observedClassLoader.set(Thread.currentThread().getContextClassLoader()); |
| 233 | + CallbackRegistration callbackRegistration = |
| 234 | + CallbackRegistration.create(Collections.singletonList(measurement2), callback); |
| 235 | + Thread.currentThread().setContextClassLoader(originalClassLoader); |
| 236 | + |
| 237 | + // Simulate invocation on a thread with null context class loader (like DaemonThreadFactory) |
| 238 | + Thread.currentThread().setContextClassLoader(null); |
| 239 | + callbackRegistration.invokeCallback(registeredReader, 0, 1); |
| 240 | + |
| 241 | + // Callback should have seen the registration-time classloader |
| 242 | + assertThat(observedClassLoader.get()).isSameAs(registrationClassLoader); |
| 243 | + |
| 244 | + // After invocation, the thread's context classloader should be restored to null |
| 245 | + assertThat(Thread.currentThread().getContextClassLoader()).isNull(); |
| 246 | + |
| 247 | + // Clean up |
| 248 | + Thread.currentThread().setContextClassLoader(originalClassLoader); |
| 249 | + } |
| 250 | + |
| 251 | + @Test |
| 252 | + void invokeCallback_RestoresContextClassLoaderOnException() { |
| 253 | + ClassLoader registrationClassLoader = new ClassLoader() {}; |
| 254 | + ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); |
| 255 | + |
| 256 | + Thread.currentThread().setContextClassLoader(registrationClassLoader); |
| 257 | + Runnable callback = |
| 258 | + () -> { |
| 259 | + throw new RuntimeException("Error!"); |
| 260 | + }; |
| 261 | + CallbackRegistration callbackRegistration = |
| 262 | + CallbackRegistration.create(Collections.singletonList(measurement2), callback); |
| 263 | + Thread.currentThread().setContextClassLoader(originalClassLoader); |
| 264 | + |
| 265 | + // Simulate invocation on a thread with null context class loader |
| 266 | + Thread.currentThread().setContextClassLoader(null); |
| 267 | + callbackRegistration.invokeCallback(registeredReader, 0, 1); |
| 268 | + |
| 269 | + // Context classloader should still be restored even after exception |
| 270 | + assertThat(Thread.currentThread().getContextClassLoader()).isNull(); |
| 271 | + |
| 272 | + // Clean up |
| 273 | + Thread.currentThread().setContextClassLoader(originalClassLoader); |
| 274 | + } |
| 275 | + |
222 | 276 | @Test |
223 | 277 | void invokeCallback_MultipleMeasurements_ThrowsException() { |
224 | 278 | Runnable callback = |
|
0 commit comments