Skip to content

Commit 56fd9f5

Browse files
authored
Introduce static ContextManagers.useClassLoader method (#101)
* Introduce static ContextManagers.useClassLoader(cl) method * Add forgotten `@since` tag * Add concurrent unit test with fixed classloader
1 parent ead72b6 commit 56fd9f5

File tree

4 files changed

+69
-5
lines changed

4 files changed

+69
-5
lines changed

context-propagation-java5/src/main/java/nl/talsmasoftware/context/ContextManagers.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,36 @@ public static void clearActiveContexts() {
145145
Timing.timed(System.nanoTime() - start, ContextManagers.class, "clearActiveContexts");
146146
}
147147

148+
/**
149+
* Override the {@linkplain ClassLoader} used to lookup {@linkplain ContextManager contextmanagers}.
150+
* <p>
151+
* Normally, taking a snapshot uses the {@linkplain Thread#getContextClassLoader() Context ClassLoader} from the
152+
* {@linkplain Thread#currentThread() current thread} to look up all {@linkplain ContextManager context managers}.
153+
* It is possible to configure a fixed, single classloader in your application for looking up the context managers.
154+
* <p>
155+
* Using this method to specify a fixed classloader will only impact
156+
* new {@linkplain ContextSnapshot context snapshots}. Existing snapshots will not be impacted.
157+
* <p>
158+
* <strong>Notes:</strong><br>
159+
* <ul>
160+
* <li>Please be aware that this configuration is global!
161+
* <li>This will also impact the lookup of
162+
* {@linkplain nl.talsmasoftware.context.observer.ContextObserver context observers}
163+
* </ul>
164+
*
165+
* @param classLoader The single, fixed ClassLoader to use for finding context managers.
166+
* Specify {@code null} to restore the default behaviour.
167+
* @since 1.0.5
168+
*/
169+
public static void useClassLoader(ClassLoader classLoader) {
170+
Level loglevel = PriorityServiceLoader.classLoaderOverride == classLoader ? Level.FINEST : Level.FINE;
171+
if (LOGGER.isLoggable(loglevel)) {
172+
LOGGER.log(loglevel, "Setting override classloader for loading ContextManager and ContextObserver " +
173+
"instances to " + classLoader + " (was: " + PriorityServiceLoader.classLoaderOverride + ").");
174+
}
175+
PriorityServiceLoader.classLoaderOverride = classLoader;
176+
}
177+
148178
private static void clearContext(ContextManager manager, Context context) {
149179
final long start = System.nanoTime();
150180
final Class<? extends Context> contextType = context.getClass();

context-propagation-java5/src/main/java/nl/talsmasoftware/context/PriorityServiceLoader.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public final class PriorityServiceLoader<SVC> implements Iterable<SVC> {
4343
private static final String ENVIRONMENT_CACHING_VALUE = System.getenv(
4444
SYSTEMPROPERTY_CACHING.replace('.', '_').toUpperCase(Locale.ENGLISH));
4545

46+
// Set from ContextManagers.useClassLoader(); sometimes a single, fixed classloader may be necessary (e.g. #97)
47+
static volatile ClassLoader classLoaderOverride = null;
4648
private final Class<SVC> serviceType;
4749
private final Map<ClassLoader, List<SVC>> cache = new WeakHashMap<ClassLoader, List<SVC>>();
4850

@@ -53,11 +55,12 @@ public PriorityServiceLoader(Class<SVC> serviceType) {
5355

5456
@SuppressWarnings("unchecked")
5557
public Iterator<SVC> iterator() {
56-
final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
57-
List<SVC> services = cache.get(contextClassLoader);
58+
final ClassLoader classLoader =
59+
classLoaderOverride == null ? Thread.currentThread().getContextClassLoader() : classLoaderOverride;
60+
List<SVC> services = cache.get(classLoader);
5861
if (services == null) {
59-
services = findServices(contextClassLoader);
60-
if (!isCachingDisabled()) cache.put(contextClassLoader, services);
62+
services = findServices(classLoader);
63+
if (!isCachingDisabled()) cache.put(classLoader, services);
6164
}
6265
return services.iterator();
6366
}

context-propagation-java5/src/test/java/nl/talsmasoftware/context/ContextManagersTest.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2018 Talsma ICT
2+
* Copyright 2016-2019 Talsma ICT
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -75,6 +75,12 @@ public void resetContexts() {
7575
DummyContext.reset();
7676
}
7777

78+
@Before
79+
@After
80+
public void resetContextClassLoader() {
81+
ContextManagers.useClassLoader(null);
82+
}
83+
7884
@Test
7985
public void testUnsupportedConstructor() {
8086
Constructor<?>[] constructors = ContextManagers.class.getDeclaredConstructors();
@@ -273,4 +279,27 @@ public void testClearManagedContexts_withoutContextManagers() {
273279
assertThat("Restore service file!", TMP_SERVICE_FILE.renameTo(SERVICE_FILE), is(true));
274280
}
275281
}
282+
283+
@Test
284+
public void testConcurrentSnapshots_fixedClassLoader() throws ExecutionException, InterruptedException {
285+
ContextManagers.useClassLoader(Thread.currentThread().getContextClassLoader());
286+
int threadcount = 25;
287+
ExecutorService threadpool = Executors.newFixedThreadPool(threadcount);
288+
try {
289+
List<Future<ContextSnapshot>> snapshots = new ArrayList<Future<ContextSnapshot>>(threadcount);
290+
for (int i = 0; i < threadcount; i++) {
291+
snapshots.add(threadpool.submit(new Callable<ContextSnapshot>() {
292+
public ContextSnapshot call() throws Exception {
293+
return ContextManagers.createContextSnapshot();
294+
}
295+
}));
296+
}
297+
298+
for (int i = 0; i < threadcount; i++) {
299+
assertThat(snapshots.get(i).get(), is(notNullValue()));
300+
}
301+
} finally {
302+
threadpool.shutdown();
303+
}
304+
}
276305
}

context-propagation-java8/src/main/java/nl/talsmasoftware/context/futures/ContextAwareCompletableFuture.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ public static <U> ContextAwareCompletableFuture<U> supplyAsync(Supplier<U> suppl
158158
* @param <U> the function's return type
159159
* @return The new CompletableFuture that propagates the specified context snapshot
160160
* @see CompletableFuture#supplyAsync(Supplier, Executor)
161+
* @since 1.0.4
161162
*/
162163
public static <U> ContextAwareCompletableFuture<U> supplyAsync(
163164
Supplier<U> supplier, Executor executor, ContextSnapshot snapshot, boolean takeNewSnapshot) {
@@ -242,6 +243,7 @@ public static ContextAwareCompletableFuture<Void> runAsync(Runnable runnable, Ex
242243
* If {@code true}, a new snapshot is taken after each completion stage to propagate into the next.
243244
* @return The new CompletableFuture that propagates a snapshot of the current context
244245
* @see CompletableFuture#runAsync(Runnable, Executor)
246+
* @since 1.0.4
245247
*/
246248
public static ContextAwareCompletableFuture<Void> runAsync(
247249
Runnable runnable, Executor executor, ContextSnapshot snapshot, boolean takeNewSnapshot) {

0 commit comments

Comments
 (0)