Skip to content

Commit

Permalink
Add Spring Boot 3.2 support (#42)
Browse files Browse the repository at this point in the history
* Add spring boot 3.2 support and update some dependencies.

* Add proper support for @scheduled annotation processing by Spring Boot 3.2

* Fix checkstyle
  • Loading branch information
tw-peeterkarolin authored Feb 28, 2024
1 parent cfe0ff2 commit 15de556
Show file tree
Hide file tree
Showing 11 changed files with 101 additions and 43 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ jobs:
max-parallel: 100
matrix:
spring_boot_version:
- 3.2.3
- 3.1.2
- 3.0.7
- 2.7.13
- 2.6.15
env:
SPRING_BOOT_VERSION: ${{ matrix.spring_boot_version }}
RUNS_IN_CI: "true"
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.14.3] - 2024-02-22

### Changed

* Add support for Spring Boot 3.2 and bump some dependencies.
* In Spring Boot 3.2 logic for assigning a scheduler for executing `@Scheduled` annotated methods changed, so needed to refactored auto configuration and shutdown logic of `TaskSchedulersGracefulShutdownStrategy` to always ask the `ScheduledTaskRegistrar` for the actual `TaskScheduler` before shutting it down.
* Add support for `TaskSchedulerRouter` in `TaskSchedulersGracefulShutdownStrategy`

## [2.14.2] - 2023-07-28

### Added
Expand Down
6 changes: 3 additions & 3 deletions build.libraries.gradle
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
ext {
springBootVersion = "${System.getenv("SPRING_BOOT_VERSION") ?: '2.6.15'}"
springBootVersion = "${System.getenv("SPRING_BOOT_VERSION") ?: '2.7.13'}"

libraries = [
// version defined
awaitility : 'org.awaitility:awaitility:4.2.0',
dbScheduler : 'com.github.kagkarlsson:db-scheduler:12.1.0',
dbSchedulerSpringBootStarter : 'com.github.kagkarlsson:db-scheduler-spring-boot-starter:12.1.0',
guava : 'com.google.guava:guava:31.1-jre',
guava : 'com.google.guava:guava:33.0.0-jre',
javaxAnnotationApi : "javax.annotation:javax.annotation-api:1.3.1",
jakartaAnnotationApi : "jakarta.annotation:jakarta.annotation-api:2.1.1",
javaxServletApi : 'javax.servlet:javax.servlet-api:4.0.1',
jakartaServletApi : 'jakarta.servlet:jakarta.servlet-api:6.0.0',
spotbugsAnnotations : "com.github.spotbugs:spotbugs-annotations:${spotbugs.toolVersion.get()}",
springBootDependencies : "org.springframework.boot:spring-boot-dependencies:${springBootVersion}",
twBaseUtils : 'com.transferwise.common:tw-base-utils:1.10.0',
twBaseUtils : 'com.transferwise.common:tw-base-utils:1.12.4',

// versions managed by spring-boot-dependencies platform
flywayCore : 'org.flywaydb:flyway-core',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public KagkarlssonDbScheduledTaskShutdownStrategy kagkarlssonDbScheduledTaskShut
}

@Configuration
@ConditionalOnBean(type = "org.springframework.scheduling.TaskScheduler")
@ConditionalOnBean(type = {"org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor"})
@ConditionalOnProperty(value = "tw-graceful-shutdown.spring-task-scheduler.enabled", matchIfMissing = true)
protected static class SpringTaskSchedulerConfiguration {

Expand All @@ -126,27 +126,12 @@ public TaskSchedulersGracefulShutdownStrategy taskSchedulersGracefulShutdownStra
@Autowired GracefulShutdownProperties gracefulShutdownProperties) {
return new TaskSchedulersGracefulShutdownStrategy(applicationContext, gracefulShutdownProperties);
}
}

@Configuration
@ConditionalOnMissingBean(type = "org.springframework.scheduling.TaskScheduler")
@ConditionalOnBean(type = {"org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor",
"org.springframework.scheduling.annotation.SchedulingConfigurer"})
@ConditionalOnProperty(value = "tw-graceful-shutdown.spring-task-scheduler.enabled", matchIfMissing = true)
protected static class SpringTaskSchedulerAlternativeConfiguration {

@Bean
public TaskSchedulersGracefulShutdownStrategy taskSchedulersGracefulShutdownStrategy(
@Autowired ApplicationContext applicationContext,
@Autowired GracefulShutdownProperties gracefulShutdownProperties) {
return new TaskSchedulersGracefulShutdownStrategy(applicationContext, gracefulShutdownProperties);
}

@Bean
@Order
public SchedulingConfigurer twGsSchedulingConfigurer(TaskSchedulersGracefulShutdownStrategy taskSchedulersGracefulShutdownStrategy) {
return taskRegistrar -> taskSchedulersGracefulShutdownStrategy.addResource(taskRegistrar.getScheduler());
return taskSchedulersGracefulShutdownStrategy::setTaskRegistrar;
}

}

@Configuration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,57 @@

import com.transferwise.common.gracefulshutdown.config.GracefulShutdownProperties;
import com.transferwise.common.gracefulshutdown.utils.ExecutorShutdownUtils;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Set;
import java.util.concurrent.Executor;
import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.config.Task;
import reactor.core.publisher.Mono;

@Slf4j
public class TaskSchedulersGracefulShutdownStrategy extends BaseReactiveResourceShutdownStrategy<TaskScheduler> {

private static final Class taskSchedulerRouter;
private static final Field taskSchedulerRouterLocalExecutorField;
private static final Method taskSchedulerRouterDestoryMethod;

static {
Class clazz = null;
Field field = null;
Method method = null;
try {
clazz = Class.forName("org.springframework.scheduling.config.TaskSchedulerRouter", true,
ExecutorShutdownUtils.class.getClassLoader());
field = clazz.getDeclaredField("localExecutor");
field.setAccessible(true);
method = clazz.getDeclaredMethod("destroy");
} catch (ClassNotFoundException e) {
// ignore as it's normal in pre Spring 6.1 environment.
} catch (NoSuchFieldException | NoSuchMethodException e) {
throw new RuntimeException("org.springframework.scheduling.config.TaskSchedulerRouter class is missing expected field or method. "
+ "Contact the SRE team.", e);
}
taskSchedulerRouter = clazz;
taskSchedulerRouterLocalExecutorField = field;
taskSchedulerRouterDestoryMethod = method;
}

@Setter
private ScheduledTaskRegistrar taskRegistrar;

@Override
protected Duration getStrategyShutdownDelay() {
// Handling of @Scheduled annotation should stop immediately.
// Those are not related to clients' requests.

return Duration.ofSeconds(0);
}

Expand All @@ -30,6 +65,9 @@ protected Mono<Void> shutdownResourceGraceful(@NonNull TaskScheduler resource) {
return Mono.fromRunnable(() -> {
if (resource instanceof Executor) {
ExecutorShutdownUtils.shutdownExecutor((Executor) resource, false);
} else if (taskSchedulerRouter != null && taskSchedulerRouter.isInstance(resource)) {
log.info("Shutting down TaskSchedulerRouter {}", resource);
shutdownTaskSchedulerRouter(resource);
} else {
log.info("Shutting down unknown task scheduler '{}' using it's 'shutdown()' method.", resource);
ExecutorShutdownUtils.shutdownExecutorWithReflection(resource, true);
Expand All @@ -42,6 +80,9 @@ protected Mono<Void> shutdownResourceForced(@NonNull TaskScheduler resource) {
return Mono.fromRunnable(() -> {
if (resource instanceof Executor) {
ExecutorShutdownUtils.shutdownExecutorForced((Executor) resource);
} else if (taskSchedulerRouter != null && taskSchedulerRouter.isInstance(resource)) {
log.info("Force shutting down TaskSchedulerRouter {}", resource);
shutdownTaskSchedulerRouterForced(resource);
} else {
log.warn("Unknown TaskScheduler to force shutdown: {}. Skipping.", resource.getClass());
}
Expand All @@ -65,11 +106,32 @@ protected Mono<Boolean> getResourceForcedTerminationStatus(TaskScheduler resourc
return getResourceGracefulTerminationStatus(resource);
}

/**
* Will shut down gracefully added resources during app shutdown.
* @param taskScheduler TaskScheduler to shut down gracefully.
*/
public void addTaskScheduler(TaskScheduler taskScheduler) {
addResource(taskScheduler);
@Override
public Set<TaskScheduler> getResourcesForShutdown() {
Set<TaskScheduler> resourcesForShutdown = super.getResourcesForShutdown();
if (taskRegistrar != null) {
resourcesForShutdown.add(taskRegistrar.getScheduler());
}
return resourcesForShutdown;
}

private static void shutdownTaskSchedulerRouter(TaskScheduler scheduler) {
try {
Executor executor = (Executor) taskSchedulerRouterLocalExecutorField.get(scheduler);
if (executor != null) {
ExecutorShutdownUtils.shutdownExecutor(executor, true);
}
} catch (IllegalAccessException e) {
log.warn("Couldn't shutdown TaskSchedulerRouter during graceful shutdown", e);
}
}

private void shutdownTaskSchedulerRouterForced(TaskScheduler scheduler) {
try {
taskSchedulerRouterDestoryMethod.invoke(scheduler);
} catch (IllegalAccessException | InvocationTargetException e) {
log.warn("Couldn't force shutdown TaskSchedulerRouter during graceful shutdown", e);
}
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.transferwise.common.gracefulshutdown.utils;

import com.transferwise.common.baseutils.concurrency.ScheduledTaskExecutor;
import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
Expand All @@ -19,6 +20,7 @@ public abstract class ExecutorShutdownUtils {
* contact SRE
*/
public static void shutdownExecutor(Executor executor, boolean askToReportOnUnknownExecutor) {

if (executor instanceof ThreadPoolTaskScheduler) {
log.info("Shutting down thread pool task scheduler '{}'.", executor);
shutdownThreadPoolTaskScheduler((ThreadPoolTaskScheduler) executor);
Expand Down Expand Up @@ -139,4 +141,5 @@ private static void shutdownScheduledThreadPoolExecutor(ScheduledThreadPoolExecu
scheduledThreadPoolExecutor.getQueue().clear();
scheduledThreadPoolExecutor.shutdown();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
import com.transferwise.common.gracefulshutdown.strategies.TaskSchedulersGracefulShutdownStrategy;
import com.transferwise.common.gracefulshutdown.test.BaseTestEnvironment;
import com.transferwise.common.gracefulshutdown.test.TestApplication;
import com.transferwise.common.gracefulshutdown.test.TestBApplication;
import com.transferwise.common.gracefulshutdown.testcustomscheduler.CustomSchedulerConfiguration;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;

@BaseTestEnvironment
@SpringBootTest(classes = {TestBApplication.class})
class AlternativeSchedulingShutdownerIntTest {
@ContextConfiguration(classes = {CustomSchedulerConfiguration.class})
class CustomSchedulerShutdownerIntTest {

@Autowired
private GracefulShutdowner gracefulShutdowner;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.Status;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ActiveProfiles;

@BaseTestEnvironment
Expand All @@ -37,6 +38,8 @@ class GracefulShutdownerIntTest {
private TaskSchedulersGracefulShutdownStrategy taskSchedulersGracefulShutdownStrategy;
@Autowired
private TestApplication testApplication;
@Autowired
private ApplicationContext context;

@Test
@SneakyThrows
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public void shutdown_invoked_on_external_added_classes() {
);
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.initialize();
strategy.addTaskScheduler(threadPoolTaskScheduler);
strategy.addResource(threadPoolTaskScheduler);

// WHEN
strategy.prepareForShutdown();
Expand All @@ -74,7 +74,7 @@ public void shutdown_timeout_is_applied_and_called_shutdownNow() {
);
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.initialize();
strategy.addTaskScheduler(threadPoolTaskScheduler);
strategy.addResource(threadPoolTaskScheduler);
threadPoolTaskScheduler.execute(() -> {
try {
Thread.sleep(checkMaxWaitTime.toMillis());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package com.transferwise.common.gracefulshutdown.test;
package com.transferwise.common.gracefulshutdown.testcustomscheduler;

import java.util.concurrent.Executors;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;

// More customized scheduling is set up.
@SpringBootApplication
@EnableScheduling
public class TestBApplication {
@Configuration
public class CustomSchedulerConfiguration {

@Bean
public SchedulingConfigurer mySchedulingConfigurer() {
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version=2.14.2
version=2.14.3

0 comments on commit 15de556

Please sign in to comment.