Skip to content

Commit e5ea024

Browse files
authored
Add more intuitive method to wrap ExecutorService (#305)
The `ContextExecutorService#wrap` method accepting a `Supplier<ContextSnapshot>` can be a mystery for users as it's not immediately obvious that a `ContextSnapshotFactory` needs to be used to supply a `ContextSnapshot`. This change adds an overload that accepts the `ContextSnapshotFactory` explicitly and only works with ThreadLocal values as a sane default. For more advanced use cases the variant accepting `Supplier` can be used. The same variant is also added to `ContextScheduledExecutorService`.
1 parent 9f49fa3 commit e5ea024

File tree

3 files changed

+90
-1
lines changed

3 files changed

+90
-1
lines changed

context-propagation/src/main/java/io/micrometer/context/ContextExecutorService.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,35 @@ protected ContextSnapshot capture() {
139139
return this.contextSnapshot.get();
140140
}
141141

142+
/**
143+
* Wrap the given {@code ExecutorService} in order to propagate context to any
144+
* executed task through the given {@link ContextSnapshotFactory}.
145+
* <p>
146+
* This method only captures ThreadLocal value. To work with other types of contexts,
147+
* use {@link #wrap(ExecutorService, Supplier)}.
148+
* </p>
149+
* @param service the executorService to wrap
150+
* @param contextSnapshotFactory {@link ContextSnapshotFactory} for capturing a
151+
* {@link ContextSnapshot} at the point when tasks are scheduled
152+
* @return {@code ExecutorService} wrapper
153+
* @since 1.1.2
154+
*/
155+
public static ExecutorService wrap(ExecutorService service, ContextSnapshotFactory contextSnapshotFactory) {
156+
return new ContextExecutorService<>(service, contextSnapshotFactory::captureAll);
157+
}
158+
142159
/**
143160
* Wrap the given {@code ExecutorService} in order to propagate context to any
144161
* executed task through the given {@link ContextSnapshot} supplier.
162+
* <p>
163+
* Typically, a {@link ContextSnapshotFactory} can be used to supply the snapshot. In
164+
* the case that only ThreadLocal values are to be captured, the
165+
* {@link #wrap(ExecutorService, ContextSnapshotFactory)} variant can be used.
166+
* </p>
145167
* @param service the executorService to wrap
146168
* @param snapshotSupplier supplier for capturing a {@link ContextSnapshot} at the
147169
* point when tasks are scheduled
170+
* @return {@code ExecutorService} wrapper
148171
*/
149172
public static ExecutorService wrap(ExecutorService service, Supplier<ContextSnapshot> snapshotSupplier) {
150173
return new ContextExecutorService<>(service, snapshotSupplier);
@@ -154,6 +177,7 @@ public static ExecutorService wrap(ExecutorService service, Supplier<ContextSnap
154177
* Variant of {@link #wrap(ExecutorService, Supplier)} that uses
155178
* {@link ContextSnapshot#captureAll(Object...)} to create the context snapshot.
156179
* @param service the executorService to wrap
180+
* @return {@code ExecutorService} wrapper
157181
* @deprecated use {@link #wrap(ExecutorService, Supplier)}
158182
*/
159183
@Deprecated

context-propagation/src/main/java/io/micrometer/context/ContextScheduledExecutorService.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,37 @@ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialD
6464
return getExecutorService().scheduleWithFixedDelay(capture().wrap(command), initialDelay, delay, unit);
6565
}
6666

67+
/**
68+
* Wrap the given {@code ScheduledExecutorService} in order to propagate context to
69+
* any executed task through the given {@link ContextSnapshotFactory}.
70+
* <p>
71+
* This method only captures ThreadLocal value. To work with other types of contexts,
72+
* use {@link #wrap(ScheduledExecutorService, Supplier)}.
73+
* </p>
74+
* @param service the executorService to wrap
75+
* @param contextSnapshotFactory {@link ContextSnapshotFactory} for capturing a
76+
* {@link ContextSnapshot} at the point when tasks are scheduled
77+
* @return {@code ScheduledExecutorService} wrapper
78+
* @since 1.1.2
79+
*/
80+
public static ScheduledExecutorService wrap(ScheduledExecutorService service,
81+
ContextSnapshotFactory contextSnapshotFactory) {
82+
return new ContextScheduledExecutorService(service, contextSnapshotFactory::captureAll);
83+
}
84+
6785
/**
6886
* Wrap the given {@code ScheduledExecutorService} in order to propagate context to
6987
* any executed task through the given {@link ContextSnapshot} supplier.
88+
* <p>
89+
* Typically, a {@link ContextSnapshotFactory} can be used to supply the snapshot. In
90+
* the case that only ThreadLocal values are to be captured, the
91+
* {@link #wrap(ScheduledExecutorService, ContextSnapshotFactory)} variant can be
92+
* used.
93+
* </p>
7094
* @param service the executorService to wrap
7195
* @param supplier supplier for capturing a {@link ContextSnapshot} at the point when
7296
* tasks are scheduled
97+
* @return {@code ScheduledExecutorService} wrapper
7398
*/
7499
public static ScheduledExecutorService wrap(ScheduledExecutorService service, Supplier<ContextSnapshot> supplier) {
75100
return new ContextScheduledExecutorService(service, supplier);
@@ -79,6 +104,7 @@ public static ScheduledExecutorService wrap(ScheduledExecutorService service, Su
79104
* Variant of {@link #wrap(ScheduledExecutorService, Supplier)} that uses
80105
* {@link ContextSnapshot#captureAll(Object...)} to create the context snapshot.
81106
* @param service the executorService to wrap
107+
* @return {@code ScheduledExecutorService} wrapper
82108
* @deprecated use {@link #wrap(ScheduledExecutorService, Supplier)}
83109
*/
84110
@Deprecated

context-propagation/src/test/java/io/micrometer/context/ContextWrappingTests.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package io.micrometer.context;
1717

1818
import java.util.Collections;
19+
import java.util.Map;
1920
import java.util.concurrent.Callable;
2021
import java.util.concurrent.CountDownLatch;
2122
import java.util.concurrent.ExecutionException;
@@ -39,7 +40,8 @@
3940
class ContextWrappingTests {
4041

4142
private final ContextRegistry registry = new ContextRegistry()
42-
.registerThreadLocalAccessor(new StringThreadLocalAccessor());
43+
.registerThreadLocalAccessor(new StringThreadLocalAccessor())
44+
.registerContextAccessor(new TestContextAccessor());
4345

4446
private final ContextSnapshotFactory defaultSnapshotFactory = ContextSnapshotFactory.builder()
4547
.contextRegistry(registry)
@@ -140,6 +142,43 @@ void should_instrument_scheduled_executor_service()
140142
}
141143
}
142144

145+
@Test
146+
void should_instrument_scheduled_executor_service_with_snapshot_supplier()
147+
throws InterruptedException, ExecutionException, TimeoutException {
148+
Map<String, String> sourceContext = Collections.singletonMap(StringThreadLocalAccessor.KEY, "hello from map");
149+
150+
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
151+
try {
152+
StringThreadLocalHolder.setValue("hello at time of creation of the executor");
153+
AtomicReference<String> valueInNewThread = new AtomicReference<>();
154+
runInNewThread(executorService, valueInNewThread,
155+
atomic -> then(atomic.get()).as("By default thread local information should not be propagated")
156+
.isNull());
157+
158+
StringThreadLocalHolder.setValue("hello at time of creation of the executor");
159+
runInNewThread(
160+
ContextExecutorService
161+
.wrap(executorService, () -> defaultSnapshotFactory.captureAll(sourceContext)),
162+
valueInNewThread,
163+
atomic -> then(atomic.get())
164+
.as("With context container the thread local information should be propagated")
165+
.isEqualTo("hello from map"));
166+
167+
StringThreadLocalHolder.setValue("hello at time of creation of the executor");
168+
runInNewThread(
169+
ContextScheduledExecutorService
170+
.wrap(executorService, () -> defaultSnapshotFactory.captureAll(sourceContext)),
171+
valueInNewThread,
172+
atomic -> then(atomic.get())
173+
.as("With context container the thread local information should be propagated")
174+
.isEqualTo("hello from map"));
175+
}
176+
finally {
177+
executorService.shutdown();
178+
}
179+
180+
}
181+
143182
private void runInNewThread(Runnable runnable) throws InterruptedException, TimeoutException {
144183
CountDownLatch latch = new CountDownLatch(1);
145184
Thread thread = new Thread(countDownWhenDone(runnable, latch));

0 commit comments

Comments
 (0)