Add static wrappers for Executor / ExecutorService using current cont… (#2988)
* Add static wrappers for Executor / ExecutorService using current context at invocation time. * Missing javadoc * Cleanup * taskWrapping
This commit is contained in:
		
							parent
							
								
									5f32df7b9b
								
							
						
					
					
						commit
						691e24fc19
					
				|  | @ -99,6 +99,36 @@ public interface Context { | |||
|     return ArrayBasedContext.root(); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns an {@link Executor} which delegates to the provided {@code executor}, wrapping all | ||||
|    * invocations of {@link Executor#execute(Runnable)} with the {@linkplain Context#current() | ||||
|    * current context} at the time of invocation. | ||||
|    * | ||||
|    * <p>This is generally used to create an {@link Executor} which will forward the {@link Context} | ||||
|    * during an invocation to another thread. For example, you may use something like {@code Executor | ||||
|    * dbExecutor = Context.wrapTasks(threadPool)} to ensure calls like {@code dbExecutor.execute(() | ||||
|    * -> database.query())} have {@link Context} available on the thread executing database queries. | ||||
|    */ | ||||
|   static Executor taskWrapping(Executor executor) { | ||||
|     return command -> executor.execute(Context.current().wrap(command)); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns an {@link ExecutorService} which delegates to the provided {@code executorService}, | ||||
|    * wrapping all invocations of {@link ExecutorService} methods such as {@link | ||||
|    * ExecutorService#execute(Runnable)} or {@link ExecutorService#submit(Runnable)} with the | ||||
|    * {@linkplain Context#current() current context} at the time of invocation. | ||||
|    * | ||||
|    * <p>This is generally used to create an {@link ExecutorService} which will forward the {@link | ||||
|    * Context} during an invocation to another thread. For example, you may use something like {@code | ||||
|    * ExecutorService dbExecutor = Context.wrapTasks(threadPool)} to ensure calls like {@code | ||||
|    * dbExecutor.execute(() -> database.query())} have {@link Context} available on the thread | ||||
|    * executing database queries. | ||||
|    */ | ||||
|   static ExecutorService taskWrapping(ExecutorService executorService) { | ||||
|     return new CurrentContextExecutorService(executorService); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns the value stored in this {@link Context} for the given {@link ContextKey}, or {@code | ||||
|    * null} if there is no value for the key in this context. | ||||
|  |  | |||
|  | @ -5,7 +5,6 @@ | |||
| 
 | ||||
| package io.opentelemetry.context; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| import java.util.concurrent.Callable; | ||||
|  | @ -15,99 +14,61 @@ import java.util.concurrent.Future; | |||
| import java.util.concurrent.TimeUnit; | ||||
| import java.util.concurrent.TimeoutException; | ||||
| 
 | ||||
| class ContextExecutorService implements ExecutorService { | ||||
| class ContextExecutorService extends ForwardingExecutorService { | ||||
| 
 | ||||
|   private final Context context; | ||||
|   private final ExecutorService delegate; | ||||
| 
 | ||||
|   ContextExecutorService(Context context, ExecutorService delegate) { | ||||
|     super(delegate); | ||||
|     this.context = context; | ||||
|     this.delegate = delegate; | ||||
|   } | ||||
| 
 | ||||
|   final Context context() { | ||||
|     return context; | ||||
|   } | ||||
| 
 | ||||
|   ExecutorService delegate() { | ||||
|     return delegate; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void shutdown() { | ||||
|     delegate.shutdown(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public List<Runnable> shutdownNow() { | ||||
|     return delegate.shutdownNow(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public boolean isShutdown() { | ||||
|     return delegate.isShutdown(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public boolean isTerminated() { | ||||
|     return delegate.isTerminated(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { | ||||
|     return delegate.awaitTermination(timeout, unit); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public <T> Future<T> submit(Callable<T> task) { | ||||
|     return delegate.submit(context.wrap(task)); | ||||
|     return delegate().submit(context.wrap(task)); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public <T> Future<T> submit(Runnable task, T result) { | ||||
|     return delegate.submit(context.wrap(task), result); | ||||
|     return delegate().submit(context.wrap(task), result); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Future<?> submit(Runnable task) { | ||||
|     return delegate.submit(context.wrap(task)); | ||||
|     return delegate().submit(context.wrap(task)); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) | ||||
|       throws InterruptedException { | ||||
|     return delegate.invokeAll(wrap(tasks)); | ||||
|     return delegate().invokeAll(wrap(context, tasks)); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public <T> List<Future<T>> invokeAll( | ||||
|       Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) | ||||
|       throws InterruptedException { | ||||
|     return delegate.invokeAll(wrap(tasks), timeout, unit); | ||||
|     return delegate().invokeAll(wrap(context, tasks), timeout, unit); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public <T> T invokeAny(Collection<? extends Callable<T>> tasks) | ||||
|       throws InterruptedException, ExecutionException { | ||||
|     return delegate.invokeAny(wrap(tasks)); | ||||
|     return delegate().invokeAny(wrap(context, tasks)); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) | ||||
|       throws InterruptedException, ExecutionException, TimeoutException { | ||||
|     return delegate.invokeAny(wrap(tasks), timeout, unit); | ||||
|     return delegate().invokeAny(wrap(context, tasks), timeout, unit); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void execute(Runnable command) { | ||||
|     delegate.execute(context.wrap(command)); | ||||
|   } | ||||
| 
 | ||||
|   private <T> Collection<? extends Callable<T>> wrap(Collection<? extends Callable<T>> tasks) { | ||||
|     List<Callable<T>> wrapped = new ArrayList<>(); | ||||
|     for (Callable<T> task : tasks) { | ||||
|       wrapped.add(context.wrap(task)); | ||||
|     } | ||||
|     return wrapped; | ||||
|     delegate().execute(context.wrap(command)); | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,67 @@ | |||
| /* | ||||
|  * Copyright The OpenTelemetry Authors | ||||
|  * SPDX-License-Identifier: Apache-2.0 | ||||
|  */ | ||||
| 
 | ||||
| package io.opentelemetry.context; | ||||
| 
 | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| import java.util.concurrent.Callable; | ||||
| import java.util.concurrent.ExecutionException; | ||||
| import java.util.concurrent.ExecutorService; | ||||
| import java.util.concurrent.Future; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| import java.util.concurrent.TimeoutException; | ||||
| 
 | ||||
| final class CurrentContextExecutorService extends ForwardingExecutorService { | ||||
| 
 | ||||
|   CurrentContextExecutorService(ExecutorService delegate) { | ||||
|     super(delegate); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public <T> Future<T> submit(Callable<T> task) { | ||||
|     return delegate().submit(Context.current().wrap(task)); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public <T> Future<T> submit(Runnable task, T result) { | ||||
|     return delegate().submit(Context.current().wrap(task), result); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public Future<?> submit(Runnable task) { | ||||
|     return delegate().submit(Context.current().wrap(task)); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) | ||||
|       throws InterruptedException { | ||||
|     return delegate().invokeAll(wrap(Context.current(), tasks)); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public <T> List<Future<T>> invokeAll( | ||||
|       Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) | ||||
|       throws InterruptedException { | ||||
|     return delegate().invokeAll(wrap(Context.current(), tasks), timeout, unit); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public <T> T invokeAny(Collection<? extends Callable<T>> tasks) | ||||
|       throws InterruptedException, ExecutionException { | ||||
|     return delegate().invokeAny(wrap(Context.current(), tasks)); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) | ||||
|       throws InterruptedException, ExecutionException, TimeoutException { | ||||
|     return delegate().invokeAny(wrap(Context.current(), tasks), timeout, unit); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void execute(Runnable command) { | ||||
|     delegate().execute(Context.current().wrap(command)); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,61 @@ | |||
| /* | ||||
|  * Copyright The OpenTelemetry Authors | ||||
|  * SPDX-License-Identifier: Apache-2.0 | ||||
|  */ | ||||
| 
 | ||||
| package io.opentelemetry.context; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| import java.util.concurrent.Callable; | ||||
| import java.util.concurrent.ExecutorService; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| 
 | ||||
| /** A {@link ExecutorService} that implements methods that don't need {@link Context}. */ | ||||
| abstract class ForwardingExecutorService implements ExecutorService { | ||||
| 
 | ||||
|   private final ExecutorService delegate; | ||||
| 
 | ||||
|   protected ForwardingExecutorService(ExecutorService delegate) { | ||||
|     this.delegate = delegate; | ||||
|   } | ||||
| 
 | ||||
|   ExecutorService delegate() { | ||||
|     return delegate; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public final void shutdown() { | ||||
|     delegate.shutdown(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public final List<Runnable> shutdownNow() { | ||||
|     return delegate.shutdownNow(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public final boolean isShutdown() { | ||||
|     return delegate.isShutdown(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public final boolean isTerminated() { | ||||
|     return delegate.isTerminated(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public final boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { | ||||
|     return delegate.awaitTermination(timeout, unit); | ||||
|   } | ||||
| 
 | ||||
|   protected static <T> Collection<? extends Callable<T>> wrap( | ||||
|       Context context, Collection<? extends Callable<T>> tasks) { | ||||
|     List<Callable<T>> wrapped = new ArrayList<>(); | ||||
|     for (Callable<T> task : tasks) { | ||||
|       wrapped.add(context.wrap(task)); | ||||
|     } | ||||
|     return wrapped; | ||||
|   } | ||||
| } | ||||
|  | @ -217,6 +217,11 @@ class ContextTest { | |||
| 
 | ||||
|     executor.execute(callback); | ||||
|     assertThat(value).hasValue(null); | ||||
| 
 | ||||
|     try (Scope ignored = CAT.makeCurrent()) { | ||||
|       Context.taskWrapping(executor).execute(callback); | ||||
|       assertThat(value).hasValue("cat"); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Nested | ||||
|  | @ -227,10 +232,14 @@ class ContextTest { | |||
|     protected ExecutorService wrapped; | ||||
|     protected AtomicReference<String> value; | ||||
| 
 | ||||
|     protected ExecutorService wrap(ExecutorService executorService) { | ||||
|       return CAT.wrap(executorService); | ||||
|     } | ||||
| 
 | ||||
|     @BeforeAll | ||||
|     void initExecutor() { | ||||
|       executor = Executors.newSingleThreadScheduledExecutor(); | ||||
|       wrapped = CAT.wrap((ExecutorService) executor); | ||||
|       wrapped = wrap(executor); | ||||
|     } | ||||
| 
 | ||||
|     @AfterAll | ||||
|  | @ -358,6 +367,30 @@ class ContextTest { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Nested | ||||
|   @TestInstance(Lifecycle.PER_CLASS) | ||||
|   class CurrentContextWrappingExecutorService extends WrapExecutorService { | ||||
|     @Override | ||||
|     protected ExecutorService wrap(ExecutorService executorService) { | ||||
|       return Context.taskWrapping(executorService); | ||||
|     } | ||||
| 
 | ||||
|     private Scope scope; | ||||
| 
 | ||||
|     @BeforeEach | ||||
|     // Closed in AfterEach | ||||
|     @SuppressWarnings("MustBeClosedChecker") | ||||
|     void makeCurrent() { | ||||
|       scope = CAT.makeCurrent(); | ||||
|     } | ||||
| 
 | ||||
|     @AfterEach | ||||
|     void close() { | ||||
|       scope.close(); | ||||
|       scope = null; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Test | ||||
|   void keyToString() { | ||||
|     assertThat(ANIMAL.toString()).isEqualTo("animal"); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue