From c399b5bcf406c02b30799eeccafd45b637fae65e Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Wed, 13 Feb 2019 16:23:39 +0100 Subject: [PATCH] Changed the way the default executor is picked; fixes gh-1212 --- .../async/AsyncDefaultAutoConfiguration.java | 67 ++++++- .../async/issues/issue1212/GH1212Tests.java | 181 ++++++++++++++++++ 2 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue1212/GH1212Tests.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java index b6f83662b..36bd9bc45 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/async/AsyncDefaultAutoConfiguration.java @@ -20,8 +20,13 @@ import java.util.concurrent.Executor; import brave.Tracer; import brave.Tracing; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.aop.interceptor.AsyncExecutionAspectSupport; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -32,6 +37,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Role; import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.annotation.AsyncConfigurerSupport; @@ -70,12 +76,71 @@ public class AsyncDefaultAutoConfiguration { @Role(BeanDefinition.ROLE_INFRASTRUCTURE) static class DefaultAsyncConfigurerSupport extends AsyncConfigurerSupport { + private static final Log log = LogFactory + .getLog(DefaultAsyncConfigurerSupport.class); + @Autowired private BeanFactory beanFactory; @Override public Executor getAsyncExecutor() { - return new LazyTraceExecutor(this.beanFactory, new SimpleAsyncTaskExecutor()); + Executor delegate = getDefaultExecutor(); + return new LazyTraceExecutor(this.beanFactory, delegate); + } + + /** + * Retrieve or build a default executor for this advice instance. An executor + * returned from here will be cached for further use. + *

+ * The default implementation searches for a unique {@link TaskExecutor} bean in + * the context, or for an {@link Executor} bean named "taskExecutor" otherwise. If + * neither of the two is resolvable, this implementation will return {@code null}. + * @return the default executor, or {@code null} if none available + * @see AsyncExecutionAspectSupport#getDefaultExecutor(org.springframework.beans.factory.BeanFactory) + */ + private Executor getDefaultExecutor() { + try { + // Search for TaskExecutor bean... not plain Executor since that would + // match with ScheduledExecutorService as well, which is unusable for + // our purposes here. TaskExecutor is more clearly designed for it. + return this.beanFactory.getBean(TaskExecutor.class); + } + catch (NoUniqueBeanDefinitionException ex) { + log.debug("Could not find unique TaskExecutor bean", ex); + try { + return this.beanFactory.getBean( + AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME, + Executor.class); + } + catch (NoSuchBeanDefinitionException ex2) { + if (log.isInfoEnabled()) { + log.info( + "More than one TaskExecutor bean found within the context, and none is named " + + "'taskExecutor'. Mark one of them as primary or name it 'taskExecutor' (possibly " + + "as an alias) in order to use it for async processing: " + + ex.getBeanNamesFound()); + } + } + } + catch (NoSuchBeanDefinitionException ex) { + log.debug("Could not find default TaskExecutor bean", ex); + try { + return this.beanFactory.getBean( + AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME, + Executor.class); + } + catch (NoSuchBeanDefinitionException ex2) { + log.info("No task executor bean found for async processing: " + + "no bean of type TaskExecutor and no bean named 'taskExecutor' either"); + } + // Giving up -> either using local default executor or none at all... + } + // backward compatibility + if (log.isInfoEnabled()) { + log.info( + "For backward compatibility, will fallback to the default, SimpleAsyncTaskExecutor implementation"); + } + return new SimpleAsyncTaskExecutor(); } } diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue1212/GH1212Tests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue1212/GH1212Tests.java new file mode 100644 index 000000000..96194d3a6 --- /dev/null +++ b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/async/issues/issue1212/GH1212Tests.java @@ -0,0 +1,181 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.sleuth.instrument.async.issues.issue1212; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +import org.junit.Test; +import org.slf4j.LoggerFactory; + +import org.springframework.aop.interceptor.AsyncExecutionAspectSupport; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.AsyncConfigurerSupport; +import org.springframework.scheduling.annotation.EnableAsync; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Bertrand Renuart + */ +public class GH1212Tests { + + @Test + public void defaultTaskExecutor() throws Exception { + try (ConfigurableApplicationContext ctx = new SpringApplicationBuilder(App.class, + DefaultTaskExecutorConfig.class).run()) { + String asyncThreadName = getAsyncThreadName(ctx); + assertThat(asyncThreadName).startsWith("defaultTaskExecutor"); + } + } + + @Test + public void singleTaskExecutor() throws Exception { + try (ConfigurableApplicationContext ctx = new SpringApplicationBuilder(App.class, + SingleTaskExecutorConfig.class).run()) { + String asyncThreadName = getAsyncThreadName(ctx); + assertThat(asyncThreadName).startsWith("singleTaskExecutor"); + } + } + + @Test + public void multipleTaskExecutors() throws Exception { + try (ConfigurableApplicationContext ctx = new SpringApplicationBuilder(App.class, + MultipleTaskExecutorConfig.class).run()) { + String asyncThreadName = getAsyncThreadName(ctx); + assertThat(asyncThreadName).doesNotStartWith("multipleTaskExecutor"); + assertThat(asyncThreadName).startsWith("SimpleAsyncTaskExecutor"); // <-- + // comes + // from + // Sleuth's + // own + // AsyncConfigurer + } + } + + @Test + public void customAsyncConfigurer() throws Exception { + try (ConfigurableApplicationContext ctx = new SpringApplicationBuilder(App.class, + CustomAsyncConfigurerConfig.class).run()) { + String asyncThreadName = getAsyncThreadName(ctx); + assertThat(asyncThreadName).startsWith("customAsyncConfigurer"); + } + } + + private String getAsyncThreadName(ApplicationContext ctx) throws Exception { + return ctx.getBean(AsyncComponent.class).asyncMethod().get(); + } + + @SpringBootConfiguration + @EnableAutoConfiguration + @EnableAsync + static class App { + + @Bean + AsyncComponent asyncComponent() { + return new AsyncComponent(); + } + + } + + static class AsyncComponent { + + @Async + public CompletableFuture asyncMethod() { + LoggerFactory.getLogger("test").info("asyncMethod invoked"); + return CompletableFuture.completedFuture(Thread.currentThread().getName()); + } + + } + + /* + * Configuration with a single Executor named `taskExecutor` + */ + @Configuration + static class DefaultTaskExecutorConfig { + + @Bean(name = AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME) + public Executor taskExecutor() { + return new SimpleAsyncTaskExecutor("defaultTaskExecutor"); + } + + } + + /* + * Configuration with a single TaskExecutor + */ + @Configuration + static class SingleTaskExecutorConfig { + + @Bean + // there's the task + @Primary + public TaskExecutor singleTaskExecutor() { + return new SimpleAsyncTaskExecutor("singleTaskExecutor"); + } + + } + + /* + * Configuration with a multiple TaskExecutors --> Spring won't pick any unless one + * is @Primary + */ + @Configuration + static class MultipleTaskExecutorConfig { + + @Bean + public TaskExecutor multipleTaskExecutor1() { + return new SimpleAsyncTaskExecutor("multipleTaskExecutor1"); + } + + @Bean + public TaskExecutor multipleTaskExecutor2() { + return new SimpleAsyncTaskExecutor("multipleTaskExecutor2"); + } + + } + + /* + * Configuration where a custom AsyncConfigurer is provided + */ + @Configuration + static class CustomAsyncConfigurerConfig { + + @Bean + public AsyncConfigurer customAsyncConfigurer() { + return new AsyncConfigurerSupport() { + @Override + public Executor getAsyncExecutor() { + return new SimpleAsyncTaskExecutor("customAsyncConfigurer"); + } + }; + } + + } + +}