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

Add Spring Boot 3.2 support #42

Merged
merged 3 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading