Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to configure custom scheduler for @Scheduled annotation #34058

Closed
ahrytsiuk opened this issue Dec 9, 2024 · 1 comment
Closed

Unable to configure custom scheduler for @Scheduled annotation #34058

ahrytsiuk opened this issue Dec 9, 2024 · 1 comment
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: regression A bug that is also a regression
Milestone

Comments

@ahrytsiuk
Copy link

Since Spring 6.2.0 functionality to specify custom scheduler for @Scheduled annotation is not working anymore.

Minimal reproducible example:

import static org.assertj.core.api.Assertions.assertThat;

import java.util.HashSet;
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskHolder;
import org.springframework.scheduling.config.TaskSchedulerRouter;

class SchedulerConfigTest {

    @Test
    void withQualifiedScheduler() throws Exception {
        var ctx = new AnnotationConfigApplicationContext(QualifiedExplicitSchedulerConfig.class);
        assertThat(ctx.getBean(ScheduledTaskHolder.class).getScheduledTasks()).hasSize(2);

        Thread.sleep(110);

        assertThat(ctx.getBean("defaultSchedulerThreads", Set.class))
            .hasSizeGreaterThanOrEqualTo(1).allMatch(e -> ((String) e).startsWith("taskScheduler-"));

        assertThat(ctx.getBean("explicitSchedulerThreads", Set.class))
            .hasSizeGreaterThanOrEqualTo(1).allMatch(e -> ((String) e).startsWith("customScheduler-"));
    }

    @TestConfiguration
    @EnableScheduling
    static class QualifiedExplicitSchedulerConfig {

        public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = TaskSchedulerRouter.DEFAULT_TASK_SCHEDULER_BEAN_NAME;
        public static final String CUSTOM_TASK_SCHEDULER_BEAN_NAME = "customTaskScheduler";

        @Bean
        public Set<String> defaultSchedulerThreads() {
            return new HashSet<>();
        }

        @Bean
        public Set<String> explicitSchedulerThreads() {
            return new HashSet<>();
        }

        @Bean(name = DEFAULT_TASK_SCHEDULER_BEAN_NAME)
        public ThreadPoolTaskScheduler taskScheduler() {
            ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
            taskScheduler.setThreadNamePrefix("taskScheduler-");
            return taskScheduler;
        }

        @Bean(name = CUSTOM_TASK_SCHEDULER_BEAN_NAME)
        public SimpleAsyncTaskScheduler customTaskScheduler() {
            SimpleAsyncTaskScheduler taskScheduler = new SimpleAsyncTaskScheduler();
            taskScheduler.setThreadNamePrefix("customScheduler-");
            return taskScheduler;
        }

        @Scheduled(fixedRate = 10)
        public void task() throws Exception {
            defaultSchedulerThreads().add(Thread.currentThread().getName());
        }

        @Scheduled(fixedRate = 10, scheduler = CUSTOM_TASK_SCHEDULER_BEAN_NAME)
        public void taskWithExplicitScheduler() throws Exception {
            explicitSchedulerThreads().add(Thread.currentThread().getName());
        }

    }

}

I believe this happened in this commit dc2c8d60. Now Runnable is wrapped into OutcomeTrackingRunnable. As result TaskSchedulerRouter fails to determine qualifier:

protected String determineQualifier(Runnable task) {
     return (task instanceof SchedulingAwareRunnable sar ? sar.getQualifier() : null);
}

since OutcomeTrackingRunnable is not implementing SchedulingAwareRunnable.

@bclozel, do you think it makes sense for OutcomeTrackingRunnable to implement SchedulingAwareRunnable interface instead of Runnable? Or to have two flavours of OutcomeTrackingRunnable? And Task will wrap underlying runnable depends on underlying task?

Smth like:

public Task(Runnable runnable) {
    Assert.notNull(runnable, "Runnable must not be null");
    if (runnable instanceof SchedulingAwareRunnable sar) {
        this.runnable = new OutcomeTrackingSchedulingAwareRunnable(runnable);
    } else {
        this.runnable = new OutcomeTrackingRunnable(runnable);
    }
    this.lastExecutionOutcome = TaskExecutionOutcome.create();
}
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Dec 9, 2024
@bclozel bclozel self-assigned this Dec 9, 2024
@jhoeller jhoeller added type: regression A bug that is also a regression in: core Issues in core modules (aop, beans, core, context, expression) and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Dec 10, 2024
@jhoeller jhoeller added this to the 6.2.1 milestone Dec 10, 2024
@jhoeller
Copy link
Contributor

Looks sensible to me for OutcomeTrackingRunnable to implement SchedulingAwareRunnable and pass the isLongLived()/getQualifier() calls through to the underlying Runnable when possible or return the default values otherwise (which is technically equivalent to not implementing SchedulingAwareRunnable at all). Let's revise this for 6.2.1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) type: regression A bug that is also a regression
Projects
None yet
Development

No branches or pull requests

4 participants