1
votes

I have defined a ScheduledExecutorService bean in my Sleuth enabled Spring boot application. When I get this bean from the applicationContext sleuth has wrapped it with a LazyTraceExecutorService, this will throw a BeanNotOfRequiredTypeException.

@SpringBootTest(classes = { Config.class })
@RunWith(SpringRunner.class)
public class SleuthTest {
    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void testScheduler() {
        // this throws the BeanNotOfRequiredTypeException
        applicationContext.getBean("executorService", ScheduledExecutorService.class);
    }
}

@Configuration
@EnableAutoConfiguration
class Config {
    @Bean
    public ScheduledExecutorService executorService() {
        // Sleuth will wrap this with LazyTraceExecutor, while I would expect a TraceableScheduledExecutorService
        return new ScheduledThreadPoolExecutor(1);
    }
}

Am I doing something wrong or is this a bug?

org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'executorService' is expected to be of type 'java.util.concurrent.ScheduledExecutorService' but was actually of type 'org.springframework.cloud.sleuth.instrument.async.LazyTraceExecutor'

    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:378)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1087)
    at com.esaturnus.demotool.SleuthTest.testScheduler(SleuthTest.java:36)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:119)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:42)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
2

2 Answers

1
votes

If anyone is reading this because they are stuck with using an old version of Spring Sleuth, you can configure you task scheduling like this. (The code should go in an @Configuration @EnableScheduling class that implements SchedulingConfigurer). It works for me so far.

@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
    taskRegistrar.setTaskScheduler(taskScheduler());
}

// Declare this as a bean so that Spring shuts it down.
@Bean
public LazyTraceTaskScheduler taskScheduler() {
    ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
    // scheduledThreadCount is an auto-wired @Value for me, from application.properties.
    // You can just hard-code it.
    taskScheduler.setPoolSize(scheduledThreadCount);
    taskScheduler.setThreadNamePrefix("scheduled-");
    taskScheduler.setErrorHandler((throwable) -> 
        log.error("Uncaught error thrown by @Scheduled method", throwable));
    taskScheduler.shutdown();
    taskScheduler.initialize();
    return new LazyTraceTaskScheduler(beanFactory, taskScheduler);
}

private static class LazyTraceTaskScheduler implements TaskScheduler {

    private final BeanFactory beanFactory;
    private final ThreadPoolTaskScheduler delegate;
    private Tracer tracer;
    private TraceKeys traceKeys;
    private SpanNamer spanNamer;

    LazyTraceTaskScheduler(BeanFactory beanFactory, ThreadPoolTaskScheduler delegate) {
        this.beanFactory = beanFactory;
        this.delegate = delegate;
    }

    @Override
    public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
        return delegate.schedule(newSpanContinuingTraceRunnable(task), trigger);
    }

    @Override
    public ScheduledFuture<?> schedule(Runnable task, Date startTime) {
        return delegate.schedule(newSpanContinuingTraceRunnable(task), startTime);
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period) {
        return delegate.scheduleAtFixedRate(newSpanContinuingTraceRunnable(task), startTime, period);
    }

    @Override
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period) {
        return delegate.scheduleAtFixedRate(newSpanContinuingTraceRunnable(task), period);
    }

    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay) {
        return delegate.scheduleWithFixedDelay(newSpanContinuingTraceRunnable(task), startTime, delay);
    }

    @Override
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long delay) {
        return delegate.scheduleWithFixedDelay(newSpanContinuingTraceRunnable(task), delay);
    }

    public void shutdown() {
        delegate.shutdown();
    }

    private Runnable newSpanContinuingTraceRunnable(Runnable task) {
        if (isNull(tracer())) {
            // Due to some race conditions tracer, etc. might not be ready yet. See the code for LazyTraceExecutor.
            return task;
        }
        return new SpanContinuingTraceRunnable(tracer(), traceKeys(), spanNamer(), task);
    }

    private Tracer tracer() {
        if (isNull(tracer)) {
            try {
                tracer = beanFactory.getBean(Tracer.class);
            } catch (NoSuchBeanDefinitionException e) {
                return null;
            }
        }
        return tracer;
    }

    private TraceKeys traceKeys() {
        if (isNull(traceKeys)) {
            try {
                traceKeys = beanFactory.getBean(TraceKeys.class);
            } catch (NoSuchBeanDefinitionException e) {
                return new TraceKeys();
            }
        }
        return traceKeys;
    }

    private SpanNamer spanNamer() {
        if (isNull(spanNamer)) {
            try {
                spanNamer = beanFactory.getBean(SpanNamer.class);
            } catch (NoSuchBeanDefinitionException e) {
                return new DefaultSpanNamer();
            }
        }
        return spanNamer;
    }
}