requestMappingHandlerMapping;
+
+ /**
+ *
+ * default order,
+ * 1.ControllerEndpointHandlerMapping for @ControllerEndpoint and @RestControllerEndpoint
+ * 2.RequestMappingHandlerMapping for @RequestMapping
+ * 3.AdminControllerHandlerMapping for BootAdminServer
+ *
+ * after reorder
+ * 1.ControllerEndpointHandlerMapping for @ControllerEndpoint and @RestControllerEndpoint
+ * 2.AdminControllerHandlerMapping for BootAdminServer
+ * 3.RequestMappingHandlerMapping for @RequestMapping
+ *
+ */
+ @Test
+ @TmsLink("C13121")
+ public void mappingOrder() {
+ int good = 0;
+ for (RequestMappingHandlerMapping mapping : requestMappingHandlerMapping) {
+ log.info("requestMappingHandlerMapping class=" + mapping.getClass().getName());
+ if (mapping instanceof AdminControllerHandlerMapping) {
+ good = 1;
+ }
+ else {
+ if (good > 0) good++;
+ }
+ }
+ Assertions.assertTrue(good >= 2);
+ }
+}
diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarFirstBloodConfiguration.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarFirstBloodConfiguration.java
index d62970b95..b111a1b20 100644
--- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarFirstBloodConfiguration.java
+++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarFirstBloodConfiguration.java
@@ -6,9 +6,11 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.ModelAndView;
import pro.fessional.mirana.code.RandCode;
+import pro.fessional.wings.silencer.spring.WingsOrdered;
import pro.fessional.wings.silencer.spring.boot.ConditionalWingsEnabled;
import pro.fessional.wings.slardar.concur.impl.FirstBloodHandler;
import pro.fessional.wings.slardar.concur.impl.FirstBloodImageHandler;
@@ -33,6 +35,7 @@ public class SlardarFirstBloodConfiguration {
@Bean
@ConditionalWingsEnabled(abs = SlardarEnabledProp.Key$firstBloodImage)
+ @Order(WingsOrdered.Lv4Application)
public FirstBloodImageHandler firstBloodImageHandler(@Autowired(required = false) WingsRemoteResolver remoteResolver, SlardarFirstBloodProp prop) {
log.info("SlardarWebmvc spring-bean firstBloodImageHandler");
final FirstBloodImageHandler handler = new FirstBloodImageHandler();
diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarSessionConfiguration.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarSessionConfiguration.java
index a1c473d7b..f189a683d 100644
--- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarSessionConfiguration.java
+++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarSessionConfiguration.java
@@ -9,6 +9,7 @@
import org.springframework.boot.web.servlet.server.Session.Cookie;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
import org.springframework.session.web.http.CookieHttpSessionIdResolver;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
@@ -16,6 +17,7 @@
import org.springframework.session.web.http.HttpSessionIdResolver;
import org.springframework.util.StringUtils;
import pro.fessional.mirana.best.AssertArgs;
+import pro.fessional.wings.silencer.spring.WingsOrdered;
import pro.fessional.wings.silencer.spring.boot.ConditionalWingsEnabled;
import pro.fessional.wings.slardar.session.WingsSessionIdResolver;
import pro.fessional.wings.slardar.spring.prop.SlardarEnabledProp;
@@ -93,6 +95,7 @@ public WingsSessionIdResolver httpSessionIdResolver(
@Bean
@ConditionalWingsEnabled
+ @Order(WingsOrdered.Lv4Application + 10)
public DefaultCookieSerializerCustomizer slardarCookieSerializerCustomizer(SlardarSessionProp prop) {
log.info("SlardarWebmvc spring-bean slardarCookieSerializerCustomizer");
return it -> {
diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarTerminalConfiguration.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarTerminalConfiguration.java
index 793c21ed2..551528572 100644
--- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarTerminalConfiguration.java
+++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarTerminalConfiguration.java
@@ -5,12 +5,16 @@
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
+import pro.fessional.wings.silencer.spring.WingsOrdered;
import pro.fessional.wings.silencer.spring.boot.ConditionalWingsEnabled;
import pro.fessional.wings.slardar.constants.SlardarServletConst;
import pro.fessional.wings.slardar.context.SecurityContextUtil;
import pro.fessional.wings.slardar.context.TerminalInterceptor;
+import pro.fessional.wings.slardar.context.TerminalInterceptor.TerminalBuilder;
+import pro.fessional.wings.slardar.context.TerminalInterceptor.TerminalLogger;
import pro.fessional.wings.slardar.context.TerminalSecurityAttribute;
import pro.fessional.wings.slardar.security.WingsUserDetails;
import pro.fessional.wings.slardar.servlet.resolver.WingsLocaleResolver;
@@ -18,7 +22,9 @@
import pro.fessional.wings.slardar.spring.prop.SlardarEnabledProp;
import pro.fessional.wings.slardar.spring.prop.SlardarTerminalProp;
+import java.time.ZoneId;
import java.util.ArrayList;
+import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
@@ -37,7 +43,8 @@ public class SlardarTerminalConfiguration {
@Bean
@ConditionalWingsEnabled
- public TerminalInterceptor.TerminalBuilder remoteTerminalBuilder(WingsRemoteResolver resolver) {
+ @Order(WingsOrdered.Lv4Application + 10)
+ public TerminalBuilder remoteTerminalBuilder(WingsRemoteResolver resolver) {
log.info("SlardarWebmvc spring-bean remoteTerminalBuilder");
return (builder, request) -> builder
.terminal(TerminalAddr, resolver.resolveRemoteIp(request))
@@ -46,11 +53,13 @@ public TerminalInterceptor.TerminalBuilder remoteTerminalBuilder(WingsRemoteReso
@Bean
@ConditionalWingsEnabled
- public TerminalInterceptor.TerminalBuilder securityTerminalBuilder(WingsLocaleResolver resolver) {
+ @Order(WingsOrdered.Lv4Application + 20)
+ public TerminalBuilder securityTerminalBuilder(SlardarTerminalProp prop, WingsLocaleResolver resolver) {
log.info("SlardarWebmvc spring-bean securityTerminalBuilder");
return (builder, request) -> {
final Authentication authn = SecurityContextUtil.getAuthentication(false);
final WingsUserDetails details = SecurityContextUtil.getUserDetails(authn);
+
if (details == null) {
final Long userId = (Long) request.getAttribute(SlardarServletConst.AttrUserId);
final var locale = resolver.resolveI18nContext(request, userId);
@@ -59,8 +68,16 @@ public TerminalInterceptor.TerminalBuilder securityTerminalBuilder(WingsLocaleRe
.userOrGuest(userId);
}
else {
- builder.locale(details.getLocale())
- .timeZone(details.getZoneId())
+ Locale lcl = details.getLocale();
+ ZoneId zid = details.getZoneId();
+ if (prop.isLocaleRequest() || prop.isTimezoneRequest()) {
+ final var locale = resolver.resolveI18nContext(request, details.getUserId());
+ if (prop.isLocaleRequest()) lcl = locale.getLocale();
+ if (prop.isTimezoneRequest()) zid = details.getZoneId();
+ }
+
+ builder.locale(lcl)
+ .timeZone(zid)
.user(details.getUserId())
.authType(details.getAuthType())
.username(details.getUsername())
@@ -75,7 +92,7 @@ public TerminalInterceptor.TerminalBuilder securityTerminalBuilder(WingsLocaleRe
@Bean
@ConditionalWingsEnabled
- public TerminalInterceptor terminalInterceptor(SlardarTerminalProp prop, ObjectProvider builders, ObjectProvider loggers) {
+ public TerminalInterceptor terminalInterceptor(SlardarTerminalProp prop, ObjectProvider builders, ObjectProvider loggers) {
log.info("SlardarWebmvc spring-bean terminalInterceptor");
final TerminalInterceptor bean = new TerminalInterceptor();
diff --git a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/prop/SlardarTerminalProp.java b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/prop/SlardarTerminalProp.java
index d60c216b0..79cd9c613 100644
--- a/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/prop/SlardarTerminalProp.java
+++ b/wings/slardar-webmvc/src/main/java/pro/fessional/wings/slardar/spring/prop/SlardarTerminalProp.java
@@ -32,4 +32,20 @@ public class SlardarTerminalProp {
*/
private Map includePatterns = Collections.emptyMap();
public static final String Key$includePatterns = Key + ".include-patterns";
+
+ /**
+ * whether to set locale from request first than server
+ *
+ * @see #Key$localeRequest
+ */
+ private boolean localeRequest = true;
+ public static final String Key$localeRequest = Key + ".locale-request";
+
+ /**
+ * whether to set timezone from request first than server
+ *
+ * @see #Key$timezoneRequest
+ */
+ private boolean timezoneRequest = false;
+ public static final String Key$timezoneRequest = Key + ".timezone-request";
}
diff --git a/wings/slardar-webmvc/src/main/resources/wings-conf/wings-terminal-79.properties b/wings/slardar-webmvc/src/main/resources/wings-conf/wings-terminal-79.properties
index 4d583175e..a8797bfa0 100644
--- a/wings/slardar-webmvc/src/main/resources/wings-conf/wings-terminal-79.properties
+++ b/wings/slardar-webmvc/src/main/resources/wings-conf/wings-terminal-79.properties
@@ -2,5 +2,12 @@
wings.slardar.terminal.exclude-patterns[error]=/error
wings.slardar.terminal.exclude-patterns[api]=/api/**
wings.slardar.terminal.exclude-patterns[oauth]=/oauth/**
+
## exclude takes precedence over include
#wings.slardar.terminal.include-patterns[oauth]=
+
+## whether to set locale from request first than server
+wings.slardar.terminal.locale-request=true
+
+## whether to set timezone from request first than server
+wings.slardar.terminal.timezone-request=false
diff --git a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/app/controller/TestAsyncController.java b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/app/controller/TestAsyncController.java
index 070998acb..293b054ee 100644
--- a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/app/controller/TestAsyncController.java
+++ b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/app/controller/TestAsyncController.java
@@ -29,13 +29,13 @@ public class TestAsyncController {
@RequestMapping(value = "/test/asyn-type.json")
public CompletableFuture testAsyncType(@RequestParam("err") AsyncType err) {
log.info("testAsyncType type={}", err);
- return testAsyncService.asyncType(err);
+ return testAsyncService.typeAsync(err);
}
@RequestMapping(value = "/test/asyn-void.json")
public CompletableFuture testAsyncVoid(@RequestParam("err") AsyncType err) {
log.info("testAsyncVoid type={}", err);
- testAsyncService.asyncVoid(err);
+ testAsyncService.voidAsync(err);
return CompletableFuture.completedFuture(err.name());
}
@@ -43,7 +43,7 @@ public CompletableFuture testAsyncVoid(@RequestParam("err") AsyncType er
public DeferredResult testAsyncDefer(@RequestParam("err") AsyncType err) {
DeferredResult result = new DeferredResult<>(1000L);
log.info("testAsyncDefer type={}", err);
- testAsyncService.asyncDefer(result, err);
+ testAsyncService.deferAsync(result, err);
return result;
}
diff --git a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/app/controller/TestDoubleKillController.java b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/app/controller/TestDoubleKillController.java
index 96e700c81..e9091ff35 100644
--- a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/app/controller/TestDoubleKillController.java
+++ b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/app/controller/TestDoubleKillController.java
@@ -1,6 +1,8 @@
package pro.fessional.wings.slardar.app.controller;
+import jakarta.servlet.http.HttpServletRequest;
import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -14,6 +16,7 @@
* @since 2021-02-01
*/
@RestController
+@Slf4j
public class TestDoubleKillController {
@Setter(onMethod_ = {@Autowired})
@@ -21,8 +24,9 @@ public class TestDoubleKillController {
@GetMapping("/test/double-kill.json")
@DoubleKill(expression = "@httpSessionIdResolver.resolveSessionIds(#p0)")
- public R doubleKill() {
+ public R doubleKill(HttpServletRequest requiredBySpel) {
Sleep.ignoreInterrupt(10_000);
+ log.info("just log uri={}", requiredBySpel.getRequestURI());
return R.ok("login page");
}
diff --git a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/app/service/TestAsyncService.java b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/app/service/TestAsyncService.java
index 145d171f8..8f3b1cbbb 100644
--- a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/app/service/TestAsyncService.java
+++ b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/app/service/TestAsyncService.java
@@ -1,6 +1,7 @@
package pro.fessional.wings.slardar.app.service;
import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Assertions;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.DeferredResult;
@@ -25,13 +26,13 @@ public enum AsyncType {
public static String UserIdFailedFuture = "asyncType FailedFuture";
@Async
- public CompletableFuture asyncType(AsyncType type) {
+ public CompletableFuture typeAsync(AsyncType type) {
final String name = Thread.currentThread().getName();
if (name.contains("exec-")) {
log.info("asyncType={}", type);
}
else {
- log.error("bad thread name prefix, asyncType should contain 'exec-'");
+ Assertions.fail("bad thread prefix, should start with 'exec-'");
}
return switch (type) {
@@ -45,7 +46,7 @@ public CompletableFuture asyncType(AsyncType type) {
public static String VoidFailedFuture = "asyncVoid FailedFuture";
@Async
- public void asyncVoid(AsyncType type) {
+ public void voidAsync(AsyncType type) {
syncResult(type);
}
@@ -66,7 +67,7 @@ public String syncResult(AsyncType type) {
log.info("asyncVoid");
}
else {
- log.error("bad thread name prefix, asyncVoid should contain 'exec-'");
+ Assertions.fail("bad thread prefix, should start with 'exec-'");
}
if (type == AsyncType.UncaughtException) {
@@ -79,13 +80,13 @@ else if (type == AsyncType.FailedFuture) {
}
@Async
- public void asyncDefer(DeferredResult result, AsyncType type) {
+ public void deferAsync(DeferredResult result, AsyncType type) {
final String name = Thread.currentThread().getName();
if (name.contains("exec-")) {
log.info("asyncVoid");
}
else {
- log.error("bad thread name prefix, asyncVoid should contain 'exec-'");
+ Assertions.fail("bad thread prefix, should start with 'exec-'");
}
if (type == AsyncType.UncaughtException) {
diff --git a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/webmvc/AsyncControllerTest.java b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/webmvc/AsyncControllerTest.java
index 8fa0f48b4..879d6fd03 100644
--- a/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/webmvc/AsyncControllerTest.java
+++ b/wings/slardar-webmvc/src/test/java/pro/fessional/wings/slardar/webmvc/AsyncControllerTest.java
@@ -100,6 +100,9 @@ private void testMock(String url, String type, boolean err) throws Exception {
// [root]
Assertions.assertTrue(e.getMessage().contains("timeToWait"));
}
+ else {
+ Assertions.fail(e);
+ }
}
}
}
diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/AsyncHelper.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/AsyncHelper.java
new file mode 100644
index 000000000..c78499f30
--- /dev/null
+++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/AsyncHelper.java
@@ -0,0 +1,92 @@
+package pro.fessional.wings.slardar.async;
+
+import com.alibaba.ttl.threadpool.TtlExecutors;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.boot.task.ThreadPoolTaskExecutorBuilder;
+import org.springframework.core.task.AsyncTaskExecutor;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.function.Supplier;
+
+/**
+ * ...
+ *
+ * @author trydofor
+ * @see TtlExecutors
+ * @since 2024-05-13
+ */
+public class AsyncHelper {
+
+ protected static Executor AsyncExecutor;
+ protected static AsyncTaskExecutor AppTaskExecutor;
+
+ protected AsyncHelper(Executor asy, AsyncTaskExecutor app) {
+ AsyncExecutor = asy;
+ AppTaskExecutor = app;
+ }
+
+ /**
+ * @see org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor#DEFAULT_TASK_EXECUTOR_BEAN_NAME
+ */
+ @NotNull
+ public static Executor Async() {
+ if (AsyncExecutor == null) {
+ throw new IllegalStateException("AsyncExecutor must init before using");
+ }
+ return AsyncExecutor;
+ }
+
+ /**
+ * just like default @Async, but not AsyncUncaughtExceptionHandler
+ */
+ public static CompletableFuture Async(@NotNull Runnable task) {
+ return CompletableFuture.runAsync(task, AsyncExecutor);
+ }
+
+ /**
+ * just like default @Async, but not AsyncUncaughtExceptionHandler
+ */
+ public static CompletableFuture Async(@NotNull Supplier supplier) {
+ return CompletableFuture.supplyAsync(supplier, AsyncExecutor);
+ }
+
+ /**
+ * @see org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration#APPLICATION_TASK_EXECUTOR_BEAN_NAME
+ */
+ @NotNull
+ public static AsyncTaskExecutor AppTask() {
+ if (AppTaskExecutor == null) {
+ throw new IllegalStateException("AppTaskExecutor must init before using");
+ }
+
+ return AppTaskExecutor;
+ }
+
+
+ protected static ThreadPoolTaskExecutorBuilder ExecutorBuilder;
+ protected static AsyncTaskExecutor LiteExecutor;
+
+
+ /**
+ * @see org.springframework.scheduling.annotation.AsyncAnnotationBeanPostProcessor#DEFAULT_TASK_EXECUTOR_BEAN_NAME
+ */
+ @NotNull
+ public static AsyncTaskExecutor Lite() {
+ if (LiteExecutor == null) {
+ throw new IllegalStateException("LiteExecutor must init before using");
+ }
+ return LiteExecutor;
+ }
+
+ /**
+ * Get ThreadPoolTaskExecutorBuilder, IllegalStateException if nonull but null.
+ */
+ @NotNull
+ public static ThreadPoolTaskExecutorBuilder ExecutorBuilder() {
+ if (ExecutorBuilder == null) {
+ throw new IllegalStateException("LightBuilder must init before using");
+ }
+ return ExecutorBuilder;
+ }
+}
diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/TaskSchedulerHelper.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/TaskSchedulerHelper.java
index 65775fc93..697059b8d 100644
--- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/TaskSchedulerHelper.java
+++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/TaskSchedulerHelper.java
@@ -1,7 +1,7 @@
package pro.fessional.wings.slardar.async;
-import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
+import org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import pro.fessional.mirana.time.ThreadNow;
@@ -15,79 +15,102 @@
*/
public class TaskSchedulerHelper {
- protected static ThreadPoolTaskScheduler LightTasker;
- protected static ThreadPoolTaskScheduler HeavyTasker;
+ protected static ThreadPoolTaskScheduler FastScheduler;
+ protected static ThreadPoolTaskScheduler ScheduledScheduler;
- protected TaskSchedulerHelper(ThreadPoolTaskScheduler light, ThreadPoolTaskScheduler heavy) {
- LightTasker = light;
- HeavyTasker = heavy;
+ protected TaskSchedulerHelper(ThreadPoolTaskScheduler fast, ThreadPoolTaskScheduler scheduled) {
+ FastScheduler = fast;
+ ScheduledScheduler = scheduled;
+ }
+
+ /**
+ * configure TtlThreadPoolTaskScheduler by builder
+ */
+ public static TtlThreadPoolTaskScheduler Ttl(ThreadPoolTaskSchedulerBuilder builder) {
+ return builder.configure(new TtlThreadPoolTaskScheduler());
}
/**
* @see org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor#DEFAULT_TASK_SCHEDULER_BEAN_NAME
*/
- @Contract("true->!null")
- public static ThreadPoolTaskScheduler Light(boolean nonnull) {
- if (nonnull && LightTasker == null) {
- throw new IllegalStateException("LightTasker must init before using");
+ @NotNull
+ public static ThreadPoolTaskScheduler Fast() {
+ if (FastScheduler == null) {
+ throw new IllegalStateException("FastScheduler must init before using");
}
- return LightTasker;
+ return FastScheduler;
}
/**
* see NamingSlardarConst#slardarHeavyScheduler
*/
- @Contract("true->!null")
- public static ThreadPoolTaskScheduler Heavy(boolean nonnull) {
- if (nonnull && HeavyTasker == null) {
- throw new IllegalStateException("HeavyTasker must init before using");
+ @NotNull
+ public static ThreadPoolTaskScheduler Scheduled() {
+ if (ScheduledScheduler == null) {
+ throw new IllegalStateException("ScheduledScheduler must init before using");
}
- return HeavyTasker;
+ return ScheduledScheduler;
}
/**
- * Get Light Scheduler if fast, otherwise Heavy.
+ * just like default @Scheduled
+ *
+ * @see org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.DEFAULT_TASK_SCHEDULER_BEAN_NAME
*/
- @NotNull
- public static ThreadPoolTaskScheduler referScheduler(boolean fast) {
- return fast ? Light(true) : Heavy(true);
+ public static void Scheduled(@NotNull Runnable task) {
+ Scheduled().execute(task);
}
/**
- * Execute an async task immediately, `fast` means that the task will be finished soon, e.g. 10s.
+ * just like default @Scheduled
*
- * @see ThreadPoolTaskScheduler#execute(Runnable)
+ * @see org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.DEFAULT_TASK_SCHEDULER_BEAN_NAME
*/
- public static void execute(boolean fast, @NotNull Runnable task) {
- referScheduler(fast).execute(task);
+ public static ScheduledFuture> Scheduled(long delayMs, @NotNull Runnable task) {
+ return Scheduled().schedule(task, Instant.ofEpochMilli(ThreadNow.millis() + delayMs));
}
/**
- * Execute an async task after delayMs millis (ThreadNow), `fast` means that the task will be finished soon, e.g. 10s.
+ * just like default @Scheduled
*
- * @see ThreadPoolTaskScheduler#schedule(Runnable, Instant)
+ * @see org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.DEFAULT_TASK_SCHEDULER_BEAN_NAME
*/
- public static ScheduledFuture> execute(boolean fast, long delayMs, @NotNull Runnable task) {
- return referScheduler(fast).schedule(task, Instant.ofEpochMilli(ThreadNow.millis() + delayMs));
+ public static ScheduledFuture> Scheduled(Instant start, @NotNull Runnable task) {
+ return Scheduled().schedule(task, start);
}
/**
- * Execute an async task at specified instant, `fast` means that the task will be finished soon, e.g. 10s.
+ * just like default @Scheduled
*
- * @see ThreadPoolTaskScheduler#schedule(Runnable, Instant)
+ * @see org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.DEFAULT_TASK_SCHEDULER_BEAN_NAME
+ */
+ public static ScheduledFuture> Scheduled(Trigger trigger, @NotNull Runnable task) {
+ return Scheduled().schedule(task, trigger);
+ }
+
+ protected static ThreadPoolTaskSchedulerBuilder FastBuilder;
+ protected static ThreadPoolTaskSchedulerBuilder ScheduledBuilder;
+
+ /**
+ * Get Light ThreadPoolTaskSchedulerBuilder, IllegalStateException if nonull but null.
*/
- public static ScheduledFuture> execute(boolean fast, Instant start, @NotNull Runnable task) {
- return referScheduler(fast).schedule(task, start);
+ @NotNull
+ public static ThreadPoolTaskSchedulerBuilder FastBuilder() {
+ if (FastBuilder == null) {
+ throw new IllegalStateException("FastBuilder must init before using");
+ }
+ return FastBuilder;
}
/**
- * Execute an async task by given trigger, `fast` means that the task will be finished soon, e.g. 10s.
- * Note, errorHandler, unlike other methods, does not handle DelegatingErrorHandlingRunnable.
- *
- * @see ThreadPoolTaskScheduler#schedule(Runnable, Trigger)
+ * Get Light ThreadPoolTaskSchedulerBuilder, IllegalStateException if nonull but null.
*/
- public static ScheduledFuture> execute(boolean fast, Trigger trigger, @NotNull Runnable task) {
- return referScheduler(fast).schedule(task, trigger);
+ @NotNull
+ public static ThreadPoolTaskSchedulerBuilder ScheduledBuilder() {
+ if (ScheduledBuilder == null) {
+ throw new IllegalStateException("ScheduledBuilder must init before using");
+ }
+ return ScheduledBuilder;
}
}
diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/TtlTaskDecorator.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/TtlTaskDecorator.java
new file mode 100644
index 000000000..8d41bba4f
--- /dev/null
+++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/TtlTaskDecorator.java
@@ -0,0 +1,58 @@
+package pro.fessional.wings.slardar.async;
+
+import com.alibaba.ttl.TtlRunnable;
+import lombok.Getter;
+import lombok.Setter;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.core.task.TaskDecorator;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * ttl decorate first. then others
+ *
+ * @author trydofor
+ * @since 2024-05-14
+ */
+@Getter @Setter
+public class TtlTaskDecorator implements TaskDecorator {
+
+ protected final List decorators = new ArrayList<>();
+
+ protected volatile boolean releaseTtlValueReferenceAfterRun = false;
+ /**
+ * false for dev, true for product
+ */
+ protected volatile boolean idempotent = true;
+
+ public TtlTaskDecorator(Collection extends TaskDecorator> decorators) {
+ add(decorators);
+ }
+
+ public void add(Collection extends TaskDecorator> decorators) {
+ if (decorators == null) return;
+ for (TaskDecorator decorator : decorators) {
+ add(decorator);
+ }
+ }
+
+ public void add(TaskDecorator decorator) {
+ if (decorator != null && !(decorator instanceof TtlTaskDecorator)) {
+ decorators.add(decorator);
+ }
+ }
+
+ @Override
+ @NotNull
+ public Runnable decorate(@NotNull Runnable runnable) {
+ // ttl decorate first
+ runnable = TtlRunnable.get(runnable, releaseTtlValueReferenceAfterRun, idempotent);
+ // other decorate
+ for (TaskDecorator taskDecorator : decorators) {
+ runnable = taskDecorator.decorate(runnable);
+ }
+ return runnable;
+ }
+}
diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/TtlThreadPoolTaskScheduler.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/TtlThreadPoolTaskScheduler.java
index 29844a921..ce67809a9 100644
--- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/TtlThreadPoolTaskScheduler.java
+++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/async/TtlThreadPoolTaskScheduler.java
@@ -1,21 +1,10 @@
package pro.fessional.wings.slardar.async;
-import com.alibaba.ttl.TtlCallable;
-import com.alibaba.ttl.TtlRunnable;
import com.alibaba.ttl.threadpool.TtlExecutors;
-import org.jetbrains.annotations.NotNull;
-import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
-import org.springframework.util.concurrent.ListenableFuture;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Date;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionHandler;
-import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
/**
@@ -26,138 +15,9 @@
*/
public class TtlThreadPoolTaskScheduler extends ThreadPoolTaskScheduler {
- protected final boolean releaseTtlValueReferenceAfterRun;
- protected final boolean idempotent;
-
- public TtlThreadPoolTaskScheduler() {
- this(false, true);
- }
-
- public TtlThreadPoolTaskScheduler(boolean releaseTtlValueReferenceAfterRun, boolean idempotent) {
- this.idempotent = idempotent;
- this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
- }
-
- @Override
- @NotNull
- protected ExecutorService initializeExecutor(
- @NotNull ThreadFactory threadFactory,
- @NotNull RejectedExecutionHandler rejectedExecutionHandler
- ) {
- final ExecutorService es = super.initializeExecutor(threadFactory, rejectedExecutionHandler);
- return TtlExecutors.getTtlExecutorService(es);
- }
-
- @Override
- public void execute(@NotNull Runnable task) {
- super.execute(TtlRunnable.get(task, releaseTtlValueReferenceAfterRun, idempotent));
- }
-
- @Override
- @NotNull
- public Future> submit(@NotNull Runnable task) {
- return super.submit(TtlRunnable.get(task, releaseTtlValueReferenceAfterRun, idempotent));
- }
-
- @Override
- @NotNull
- public Future submit(@NotNull Callable task) {
- return super.submit(TtlCallable.get(task, releaseTtlValueReferenceAfterRun, idempotent));
- }
-
- @Override
- @NotNull
- @Deprecated
- public ListenableFuture> submitListenable(@NotNull Runnable task) {
- return super.submitListenable(TtlRunnable.get(task, releaseTtlValueReferenceAfterRun, idempotent));
- }
-
- @Override
- @NotNull
- @Deprecated
- public ListenableFuture submitListenable(@NotNull Callable task) {
- return super.submitListenable(TtlCallable.get(task, releaseTtlValueReferenceAfterRun, idempotent));
- }
-
- @Override
- public ScheduledFuture> schedule(@NotNull Runnable task, @NotNull Trigger trigger) {
- return super.schedule(TtlRunnable.get(task, releaseTtlValueReferenceAfterRun, idempotent), trigger);
- }
-
- @Override
- @NotNull
- @Deprecated
- public ScheduledFuture> schedule(@NotNull Runnable task, @NotNull Date startTime) {
- return schedule(task, startTime.toInstant());
- }
-
- @Override
- @NotNull
- @Deprecated
- public ScheduledFuture> scheduleAtFixedRate(@NotNull Runnable task, @NotNull Date startTime, long period) {
- return scheduleAtFixedRate(task, startTime.toInstant(), Duration.ofMillis(period));
- }
-
- @Override
- @NotNull
- @Deprecated
- public ScheduledFuture> scheduleAtFixedRate(@NotNull Runnable task, long period) {
- return scheduleAtFixedRate(task, Duration.ofMillis(period));
- }
-
- @Override
- @NotNull
- @Deprecated
- public ScheduledFuture> scheduleWithFixedDelay(@NotNull Runnable task, @NotNull Date startTime, long delay) {
- return scheduleWithFixedDelay(task, startTime.toInstant(), Duration.ofMillis(delay));
- }
-
- @Override
- @NotNull
- @Deprecated
- public ScheduledFuture> scheduleWithFixedDelay(@NotNull Runnable task, long delay) {
- return scheduleWithFixedDelay(task, Duration.ofMillis(delay));
- }
-
- @Override
- @NotNull
- public ScheduledFuture> schedule(@NotNull Runnable task, @NotNull Instant startTime) {
- return super.schedule(TtlRunnable.get(task, releaseTtlValueReferenceAfterRun, idempotent), startTime);
- }
-
- @Override
- @NotNull
- public ScheduledFuture> scheduleAtFixedRate(@NotNull Runnable task, @NotNull Instant startTime, @NotNull Duration period) {
- return super.scheduleAtFixedRate(TtlRunnable.get(task, releaseTtlValueReferenceAfterRun, idempotent), startTime, period);
- }
-
- @Override
- @NotNull
- public ScheduledFuture> scheduleAtFixedRate(@NotNull Runnable task, @NotNull Duration period) {
- return super.scheduleAtFixedRate(TtlRunnable.get(task, releaseTtlValueReferenceAfterRun, idempotent), period);
- }
-
- @Override
- @NotNull
- public ScheduledFuture> scheduleWithFixedDelay(@NotNull Runnable task, @NotNull Instant startTime, @NotNull Duration delay) {
- return super.scheduleWithFixedDelay(TtlRunnable.get(task, releaseTtlValueReferenceAfterRun, idempotent), startTime, delay);
- }
-
- @Override
- @NotNull
- public ScheduledFuture> scheduleWithFixedDelay(@NotNull Runnable task, @NotNull Duration delay) {
- return super.scheduleWithFixedDelay(TtlRunnable.get(task, releaseTtlValueReferenceAfterRun, idempotent), delay);
- }
-
- @Override
- @NotNull
- public Thread newThread(@NotNull Runnable task) {
- return super.newThread(TtlRunnable.get(task, releaseTtlValueReferenceAfterRun, idempotent));
- }
-
@Override
- @NotNull
- public Thread createThread(@NotNull Runnable task) {
- return super.createThread(TtlRunnable.get(task, releaseTtlValueReferenceAfterRun, idempotent));
+ protected ScheduledExecutorService createExecutor(int poolSize, ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
+ ScheduledExecutorService executor = super.createExecutor(poolSize, threadFactory, rejectedExecutionHandler);
+ return TtlExecutors.getTtlScheduledExecutorService(executor);
}
}
diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/concur/impl/DoubleKillAround.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/concur/impl/DoubleKillAround.java
index b47a2b5bd..177019c47 100644
--- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/concur/impl/DoubleKillAround.java
+++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/concur/impl/DoubleKillAround.java
@@ -1,6 +1,5 @@
package pro.fessional.wings.slardar.concur.impl;
-import com.alibaba.ttl.threadpool.TtlExecutors;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@@ -35,7 +34,6 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
/**
@@ -97,8 +95,6 @@ public Object doubleKill(ProceedingJoinPoint joinPoint) throws Throwable {
final ProgressContext.Bar bar = ProgressContext.gen(arrKey, now, ttl);
if (doubleKill.async()) {
- checkTtlExecutor();
-
asyncExecutor.execute(() -> {
try {
syncProceed(joinPoint, bar);
@@ -128,20 +124,6 @@ public Object doubleKill(ProceedingJoinPoint joinPoint) throws Throwable {
}
}
- private void checkTtlExecutor() {
- if (TtlExecutors.isTtlWrapper(asyncExecutor)) return;
-
- synchronized (evaluator) {
- if (TtlExecutors.isTtlWrapper(asyncExecutor)) return;
-
- if (asyncExecutor == null) {
- log.warn("config default Executors use newWorkStealingPool");
- asyncExecutor = Executors.newWorkStealingPool();
- }
- asyncExecutor = TtlExecutors.getTtlExecutor(asyncExecutor);
- }
- }
-
private Object syncProceed(ProceedingJoinPoint joinPoint, ProgressContext.Bar bar) throws Throwable {
try {
final Object r = joinPoint.proceed();
diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarAsyncConfiguration.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarAsyncConfiguration.java
index cde49d3d7..6161a4589 100644
--- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarAsyncConfiguration.java
+++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarAsyncConfiguration.java
@@ -11,16 +11,21 @@
import org.springframework.boot.task.ThreadPoolTaskSchedulerBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import pro.fessional.wings.silencer.spring.boot.ConditionalWingsEnabled;
+import pro.fessional.wings.slardar.async.AsyncHelper;
import pro.fessional.wings.slardar.async.TaskSchedulerHelper;
+import pro.fessional.wings.slardar.async.TtlTaskDecorator;
import pro.fessional.wings.slardar.async.TtlThreadPoolTaskScheduler;
import pro.fessional.wings.slardar.spring.prop.SlardarAsyncProp;
+import java.util.List;
import java.util.concurrent.Executor;
import static org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME;
@@ -42,20 +47,43 @@
@Configuration(proxyBeanMethods = false)
@ConditionalWingsEnabled
public class SlardarAsyncConfiguration {
- public static final String slardarHeavyScheduler = "slardarHeavyScheduler";
+ public static final String slardarFastScheduler = "slardarFastScheduler";
private static final Log log = LogFactory.getLog(SlardarAsyncConfiguration.class);
+ private final SlardarAsyncProp asyncProp;
+ private final ThreadPoolTaskSchedulerBuilder fastSchedulerBuilder;
+
+ public SlardarAsyncConfiguration(SlardarAsyncProp asyncProp) {
+ this.asyncProp = asyncProp;
+
+ ThreadPoolTaskSchedulerBuilder builder = new ThreadPoolTaskSchedulerBuilder();
+ TaskSchedulingProperties fast = asyncProp.getFast();
+ builder = builder.poolSize(fast.getPool().getSize());
+ TaskSchedulingProperties.Shutdown shutdown = fast.getShutdown();
+ builder = builder.awaitTermination(shutdown.isAwaitTermination());
+ builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
+ builder = builder.threadNamePrefix(fast.getThreadNamePrefix());
+ fastSchedulerBuilder = builder;
+ }
+
+ @Bean
+ @Primary
+ @ConditionalWingsEnabled
+ public TaskDecorator ttlTaskDecorator(List others) {
+ log.info("Slardar spring-bean ttlTaskDecorator, others count=" + others.size());
+ return new TtlTaskDecorator(others);
+ }
+
/**
* Executor in the context, regular (@Async) execution (that is @EnableAsync) will use it transparently
*/
@Bean(name = DEFAULT_TASK_EXECUTOR_BEAN_NAME)
@ConditionalWingsEnabled
public Executor taskExecutor(ThreadPoolTaskExecutorBuilder builder) {
- final ThreadPoolTaskExecutor executor = builder.build();
- executor.initialize();
- log.info("Slardar spring-bean taskExecutor via ttlExecutor, prefix=" + executor.getThreadNamePrefix());
- return TtlExecutors.getTtlExecutor(executor);
+ final ThreadPoolTaskExecutor bean = builder.build();
+ log.info("Slardar spring-bean taskExecutor of @Async, prefix=" + bean.getThreadNamePrefix());
+ return bean;
}
/**
@@ -64,46 +92,60 @@ public Executor taskExecutor(ThreadPoolTaskExecutorBuilder builder) {
@Bean(name = APPLICATION_TASK_EXECUTOR_BEAN_NAME)
@ConditionalWingsEnabled
public AsyncTaskExecutor applicationTaskExecutor(ThreadPoolTaskExecutorBuilder builder) {
- final ThreadPoolTaskExecutor executor = builder.build();
- executor.setThreadNamePrefix("app-" + executor.getThreadNamePrefix());
- executor.initialize();
- final Executor ttlExecutor = TtlExecutors.getTtlExecutor(executor);
- log.info("Slardar spring-bean applicationTaskExecutor via ttlExecutor, prefix=" + executor.getThreadNamePrefix());
- return new ConcurrentTaskExecutor(ttlExecutor);
+ final ThreadPoolTaskExecutor bean = builder.build();
+ bean.setThreadNamePrefix(asyncProp.getExecPrefix().getApplication());
+ log.info("Slardar spring-bean applicationTaskExecutor of Callable MVC, prefix=" + bean.getThreadNamePrefix());
+ return bean;
}
// Do NOT use @Primary to avoid overwriting the @Async thread pool.
@Bean(name = DEFAULT_TASK_SCHEDULER_BEAN_NAME)
@ConditionalWingsEnabled
public ThreadPoolTaskScheduler taskScheduler(ThreadPoolTaskSchedulerBuilder builder) {
- final TtlThreadPoolTaskScheduler scheduler = new TtlThreadPoolTaskScheduler();
- final TtlThreadPoolTaskScheduler bean = builder.configure(scheduler);
- log.info("Slardar spring-bean taskScheduler via TtlThreadPoolTaskScheduler, prefix=" + bean.getThreadNamePrefix());
+ final TtlThreadPoolTaskScheduler bean = TaskSchedulerHelper.Ttl(builder);
+ log.info("Slardar spring-bean taskScheduler of @Scheduled, prefix=" + bean.getThreadNamePrefix());
return bean;
}
- @Bean(name = slardarHeavyScheduler)
+ @Bean(name = slardarFastScheduler)
@ConditionalWingsEnabled
- public ThreadPoolTaskScheduler slardarHeavyScheduler(SlardarAsyncProp prop) {
- final TtlThreadPoolTaskScheduler scheduler = new TtlThreadPoolTaskScheduler();
- ThreadPoolTaskSchedulerBuilder builder = new ThreadPoolTaskSchedulerBuilder();
- final TaskSchedulingProperties heavy = prop.getHeavy();
- builder = builder.poolSize(heavy.getPool().getSize());
- TaskSchedulingProperties.Shutdown shutdown = heavy.getShutdown();
- builder = builder.awaitTermination(shutdown.isAwaitTermination());
- builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
- builder = builder.threadNamePrefix(heavy.getThreadNamePrefix());
- log.info("Slardar spring-bean slardarHeavyScheduler via TtlThreadPoolTaskExecutor, prefix=" + heavy.getThreadNamePrefix());
- return builder.configure(scheduler);
+ public ThreadPoolTaskScheduler slardarFastScheduler() {
+ TtlThreadPoolTaskScheduler bean = TaskSchedulerHelper.Ttl(fastSchedulerBuilder);
+ log.info("Slardar spring-bean slardarFastScheduler of fast Scheduled, prefix=" + bean.getThreadNamePrefix());
+ return bean;
}
@Bean
@ConditionalWingsEnabled
public TaskSchedulerHelper taskSchedulerHelper(
- @Qualifier(DEFAULT_TASK_SCHEDULER_BEAN_NAME) ThreadPoolTaskScheduler light,
- @Qualifier(slardarHeavyScheduler) ThreadPoolTaskScheduler heavy) {
+ @Qualifier(slardarFastScheduler) ThreadPoolTaskScheduler fast,
+ @Qualifier(DEFAULT_TASK_SCHEDULER_BEAN_NAME) ThreadPoolTaskScheduler scheduled,
+ ThreadPoolTaskSchedulerBuilder scheduledBuilder) {
log.info("Slardar spring-bean taskSchedulerHelper");
- return new TaskSchedulerHelper(light, heavy) {};
+ return new TaskSchedulerHelper(scheduled, fast) {{
+ FastBuilder = fastSchedulerBuilder;
+ ScheduledBuilder = scheduledBuilder;
+ }};
+ }
+
+ @Bean
+ @ConditionalWingsEnabled
+ public AsyncHelper asyncHelper(
+ @Qualifier(DEFAULT_TASK_EXECUTOR_BEAN_NAME) Executor asyncExec,
+ @Qualifier(APPLICATION_TASK_EXECUTOR_BEAN_NAME) AsyncTaskExecutor appExec,
+ ThreadPoolTaskExecutorBuilder executorBuilder
+ ) {
+ log.info("Slardar spring-bean asyncHelper");
+ final ThreadPoolTaskExecutor executor = executorBuilder.build();
+ executor.setThreadNamePrefix(asyncProp.getExecPrefix().getLite());
+ executor.initialize();
+ final Executor exec = TtlExecutors.getTtlExecutor(executor);
+ AsyncTaskExecutor liteExecutor = new ConcurrentTaskExecutor(exec);
+
+ return new AsyncHelper(asyncExec, appExec) {{
+ ExecutorBuilder = executorBuilder;
+ LiteExecutor = liteExecutor;
+ }};
}
}
diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarOkhttpConfiguration.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarOkhttpConfiguration.java
index c633d506e..1a01711dc 100644
--- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarOkhttpConfiguration.java
+++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/bean/SlardarOkhttpConfiguration.java
@@ -15,6 +15,7 @@
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
import pro.fessional.wings.silencer.runner.CommandLineRunnerOrdered;
import pro.fessional.wings.silencer.spring.WingsOrdered;
import pro.fessional.wings.silencer.spring.boot.ConditionalWingsEnabled;
@@ -61,6 +62,7 @@ public CookieJar okhttpHostCookieJar() {
@Bean
@ConditionalWingsEnabled
@ConditionalOnExpression("${" + SlardarOkhttpProp.Key$followRedirect + ":false} || ${" + SlardarOkhttpProp.Key$followRedirectSsl + ":false}")
+ @Order(WingsOrdered.Lv4Application)
public OkHttpRedirectNopInterceptor okhttpRedirectNopInterceptor() {
log.info("Slardar spring-bean okhttpRedirectNopInterceptor");
return new OkHttpRedirectNopInterceptor();
diff --git a/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/prop/SlardarAsyncProp.java b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/prop/SlardarAsyncProp.java
index 85a65e617..23f8aedd3 100644
--- a/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/prop/SlardarAsyncProp.java
+++ b/wings/slardar/src/main/java/pro/fessional/wings/slardar/spring/prop/SlardarAsyncProp.java
@@ -25,10 +25,30 @@ public class SlardarAsyncProp {
/**
- * heavy thread pool
+ * fast thread pool
*
* @see #Key$heavy
*/
- private TaskSchedulingProperties heavy;
- public static final String Key$heavy = Key + ".heavy";
+ private TaskSchedulingProperties fast;
+ public static final String Key$heavy = Key + ".fast";
+
+ /**
+ * executor prefix
+ *
+ * @see #Key$execPrefix
+ */
+ private ExecPrefix execPrefix = new ExecPrefix();
+ public static final String Key$execPrefix = Key + ".exec-prefix";
+
+ @Data
+ public static class ExecPrefix {
+ /**
+ * AsyncHelper lite Pool
+ */
+ private String lite = "lite-";
+ /**
+ * Callable MVC mapping
+ */
+ private String application = "app-exec-";
+ }
}
diff --git a/wings/slardar/src/main/resources/wings-conf/spring-task-79.properties b/wings/slardar/src/main/resources/wings-conf/spring-task-79.properties
index caab5122a..5af41ebdc 100644
--- a/wings/slardar/src/main/resources/wings-conf/spring-task-79.properties
+++ b/wings/slardar/src/main/resources/wings-conf/spring-task-79.properties
@@ -10,5 +10,5 @@ spring.task.execution.thread-name-prefix=exec-
## @EnableScheduling @Scheduled threadPoolTaskScheduler
spring.task.scheduling.pool.size=8
spring.task.scheduling.shutdown.await-termination=true
-spring.task.scheduling.shutdown.await-termination-period=30s
+spring.task.scheduling.shutdown.await-termination-period=180s
spring.task.scheduling.thread-name-prefix=task-
diff --git a/wings/slardar/src/main/resources/wings-conf/wings-async-79.properties b/wings/slardar/src/main/resources/wings-conf/wings-async-79.properties
index 358360d03..f8aa57a2a 100644
--- a/wings/slardar/src/main/resources/wings-conf/wings-async-79.properties
+++ b/wings/slardar/src/main/resources/wings-conf/wings-async-79.properties
@@ -7,8 +7,12 @@ wings.slardar.async.event.shutdown.await-termination=true
wings.slardar.async.event.shutdown.await-termination-period=60s
wings.slardar.async.event.thread-name-prefix=event-
-## heavy thread pool
-wings.slardar.async.heavy.pool.size=8
-wings.slardar.async.heavy.shutdown.await-termination=true
-wings.slardar.async.heavy.shutdown.await-termination-period=60s
-wings.slardar.async.heavy.thread-name-prefix=heavy-
+## fast task thread pool
+wings.slardar.async.fast.pool.size=8
+wings.slardar.async.fast.shutdown.await-termination=true
+wings.slardar.async.fast.shutdown.await-termination-period=60s
+wings.slardar.async.fast.thread-name-prefix=fast-
+
+## executor prefix
+wings.slardar.async.exec-prefix.lite=lit-exec-
+wings.slardar.async.exec-prefix.application=app-exec-
\ No newline at end of file
diff --git a/wings/slardar/src/test/java/pro/fessional/wings/slardar/app/service/TestAsyncService.java b/wings/slardar/src/test/java/pro/fessional/wings/slardar/app/service/TestAsyncService.java
index 4d707880b..22cdd3e65 100644
--- a/wings/slardar/src/test/java/pro/fessional/wings/slardar/app/service/TestAsyncService.java
+++ b/wings/slardar/src/test/java/pro/fessional/wings/slardar/app/service/TestAsyncService.java
@@ -1,6 +1,7 @@
package pro.fessional.wings.slardar.app.service;
import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Assertions;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import pro.fessional.wings.slardar.context.TerminalContext;
@@ -26,7 +27,7 @@ public enum AsyncType {
public static String UserIdFailedFuture = "asyncUserId FailedFuture";
@Async
- public CompletableFuture asyncUserId(AsyncType type) {
+ public CompletableFuture userIdAsync(AsyncType type) {
final String name = Thread.currentThread().getName();
final long uid;
if (name.contains("exec-")) {
@@ -35,7 +36,7 @@ public CompletableFuture asyncUserId(AsyncType type) {
log.info("asyncUserId={}", uid);
}
else {
- log.error("bad thread name prefix, asyncUserId should contain 'exec-'");
+ Assertions.fail("bad thread prefix, should start with 'exec-'");
uid = DefaultUserId.Null;
}
@@ -46,16 +47,36 @@ public CompletableFuture asyncUserId(AsyncType type) {
};
}
+ public Long userId(AsyncType type) {
+ final String name = Thread.currentThread().getName();
+ final long uid;
+ if (name.contains("exec-")) {
+ final TerminalContext.Context ctx = TerminalContext.get();
+ uid = ctx.getUserId();
+ log.info("asyncUserId={}", uid);
+ }
+ else {
+ Assertions.fail("bad thread prefix, should start with 'exec-'");
+ uid = DefaultUserId.Null;
+ }
+
+ return switch (type) {
+ case UncaughtException -> throw new RuntimeException(UserIdUncaughtException);
+ case Return -> uid;
+ case FailedFuture -> throw new RuntimeException(UserIdFailedFuture);
+ };
+ }
+
public static String VoidUncaughtException = "asyncVoid UncaughtException";
@Async
- public void asyncVoid(AsyncType type) {
+ public void voidAsync(AsyncType type) {
final String name = Thread.currentThread().getName();
if (name.contains("exec-")) {
log.info("asyncVoid");
}
else {
- log.error("bad thread name prefix, asyncVoid should contain 'exec-'");
+ Assertions.fail("bad thread prefix, should start with 'exec-'");
}
if (type == AsyncType.UncaughtException) {
diff --git a/wings/slardar/src/test/java/pro/fessional/wings/slardar/app/service/TestTtlDecorator.java b/wings/slardar/src/test/java/pro/fessional/wings/slardar/app/service/TestTtlDecorator.java
new file mode 100644
index 000000000..a8d4d8c04
--- /dev/null
+++ b/wings/slardar/src/test/java/pro/fessional/wings/slardar/app/service/TestTtlDecorator.java
@@ -0,0 +1,22 @@
+package pro.fessional.wings.slardar.app.service;
+
+import org.springframework.core.task.TaskDecorator;
+import org.springframework.stereotype.Service;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author trydofor
+ * @since 2024-05-14
+ */
+@Service
+public class TestTtlDecorator implements TaskDecorator {
+
+ public static final AtomicInteger Count = new AtomicInteger(0);
+
+ @Override
+ public Runnable decorate(Runnable runnable) {
+ Count.incrementAndGet();
+ return runnable;
+ }
+}
diff --git a/wings/slardar/src/test/java/pro/fessional/wings/slardar/async/AsyncHelperTest.java b/wings/slardar/src/test/java/pro/fessional/wings/slardar/async/AsyncHelperTest.java
new file mode 100644
index 000000000..60d97f746
--- /dev/null
+++ b/wings/slardar/src/test/java/pro/fessional/wings/slardar/async/AsyncHelperTest.java
@@ -0,0 +1,91 @@
+package pro.fessional.wings.slardar.async;
+
+import io.qameta.allure.TmsLink;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import pro.fessional.mirana.time.Sleep;
+import pro.fessional.wings.slardar.app.service.TestAsyncService;
+import pro.fessional.wings.slardar.app.service.TestAsyncService.AsyncType;
+import pro.fessional.wings.slardar.app.service.TestTtlDecorator;
+import pro.fessional.wings.slardar.context.TerminalContext;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author trydofor
+ * @since 2022-12-03
+ */
+@Slf4j
+@SpringBootTest
+public class AsyncHelperTest {
+
+ @Setter(onMethod_ = {@Autowired})
+ protected TestAsyncService testAsyncService;
+
+ @Setter(onMethod_ = {@Autowired})
+ protected TestTtlDecorator testTtlDecorator;
+
+ @Test
+ @TmsLink("C13122")
+ void testAsync() throws Exception {
+ final TerminalContext.Builder builder = new TerminalContext.Builder();
+ final long userId = 1L;
+ builder.user(userId);
+ TerminalContext.login(builder.build());
+
+ CompletableFuture uid = testAsyncService.userIdAsync(AsyncType.Return);
+ Assertions.assertEquals(userId, uid.get());
+
+ final AtomicInteger eqs1 = new AtomicInteger(0);
+ Sleep.ignoreInterrupt(500);
+ // If a non-TtlThreadPoolTaskScheduler is set up, but a ttlExecutor is used.
+ // then only one thread will succeed in TTL, others will fail
+ final var task1 = AsyncHelper.Async(() -> delayUid("TaskSchedulerTest Default", userId, eqs1));
+ task1.join();
+ Assertions.assertEquals(1, eqs1.get(), "userid not equals, see log");
+ eqs1.set(0);
+
+ // exception
+ failedFuture(AsyncHelper.Async(()->testAsyncService.userId(AsyncType.FailedFuture)), TestAsyncService.UserIdFailedFuture);
+ failedFuture(AsyncHelper.Async(()->testAsyncService.userId(AsyncType.UncaughtException)), TestAsyncService.UserIdUncaughtException);
+
+ Assertions.assertNotNull(testTtlDecorator);
+ Assertions.assertTrue(TestTtlDecorator.Count.get() > 0);
+ }
+
+ private void failedFuture(CompletableFuture> future, String msg) {
+ try {
+ future.get();
+ Assertions.fail();
+ }
+ catch (Exception e) {
+ boolean got = false;
+ if (e instanceof ExecutionException ee) {
+ if (ee.getCause() instanceof RuntimeException re) {
+ got = msg.equals(re.getMessage());
+ }
+ }
+ Assertions.assertTrue(got);
+ }
+ }
+
+ private void delayUid(String caller, long userId, AtomicInteger eqs) {
+ final String name = Thread.currentThread().getName();
+ if (name.contains("exec-")) {
+ final long ud = TerminalContext.get().getUserId();
+ log.info("{} , uid={}", caller, ud);
+ if (ud == userId) {
+ eqs.incrementAndGet();
+ }
+ }
+ else {
+ Assertions.fail("bad thread prefix, should start with 'exec-'");
+ }
+ }
+}
diff --git a/wings/slardar/src/test/java/pro/fessional/wings/slardar/async/TaskSchedulerTest.java b/wings/slardar/src/test/java/pro/fessional/wings/slardar/async/TaskSchedulerTest.java
index 8597b9abd..4dd55e2e1 100644
--- a/wings/slardar/src/test/java/pro/fessional/wings/slardar/async/TaskSchedulerTest.java
+++ b/wings/slardar/src/test/java/pro/fessional/wings/slardar/async/TaskSchedulerTest.java
@@ -45,7 +45,7 @@ void testTask() throws Exception {
builder.user(userId);
TerminalContext.login(builder.build());
- CompletableFuture uid = testAsyncService.asyncUserId(AsyncType.Return);
+ CompletableFuture uid = testAsyncService.userIdAsync(AsyncType.Return);
Assertions.assertEquals(userId, uid.get());
final AtomicInteger cnt1 = new AtomicInteger(0);
@@ -56,18 +56,22 @@ void testTask() throws Exception {
final ScheduledFuture> task1 = threadPoolTaskScheduler.scheduleWithFixedDelay(() -> delayUid("TaskSchedulerTest Default", userId, cnt1, eqs1), Duration.ofMillis(1_000));
Sleep.ignoreInterrupt(5_000);
task1.cancel(false);
+ Assertions.assertTrue(eqs1.get() > 0, "userid not equals, see log");
Assertions.assertEquals(cnt1.get(), eqs1.get(), "userid not equals, see log");
cnt1.set(0);
eqs1.set(0);
Sleep.ignoreInterrupt(500);
- final ScheduledFuture> task2 = threadPoolTaskScheduler.scheduleWithFixedDelay(TtlRunnable.get(() -> delayUid("TaskSchedulerTest TtlRun", userId, cnt1, eqs1), false, true), Duration.ofMillis(1_000));
+ final ScheduledFuture> task2 = threadPoolTaskScheduler.scheduleWithFixedDelay(
+ TtlRunnable.get(() -> delayUid("TaskSchedulerTest TtlRun", userId, cnt1, eqs1),
+ false, true), Duration.ofMillis(1_000));
Sleep.ignoreInterrupt(5_000);
task2.cancel(false);
+ Assertions.assertTrue(eqs1.get() > 0, "userid not equals, see log");
Assertions.assertEquals(cnt1.get(), eqs1.get(), "userid not equals, see log");
// exception
- failedFuture(testAsyncService.asyncUserId(AsyncType.FailedFuture), TestAsyncService.UserIdFailedFuture);
- failedFuture(testAsyncService.asyncUserId(AsyncType.UncaughtException), TestAsyncService.UserIdUncaughtException);
+ failedFuture(testAsyncService.userIdAsync(AsyncType.FailedFuture), TestAsyncService.UserIdFailedFuture);
+ failedFuture(testAsyncService.userIdAsync(AsyncType.UncaughtException), TestAsyncService.UserIdUncaughtException);
/*
* == by default ==
@@ -75,7 +79,7 @@ void testTask() throws Exception {
* public void pro.fessional.wings.slardar.app.service.TestAsyncService.asyncVoid(pro.fessional.wings.slardar.app.service.TestAsyncService$AsyncType)
* java.lang.RuntimeException: asyncVoid UncaughtException
*/
- testAsyncService.asyncVoid(AsyncType.UncaughtException);
+ testAsyncService.voidAsync(AsyncType.UncaughtException);
}
private void failedFuture(CompletableFuture> future, String msg) {
@@ -96,7 +100,7 @@ private void failedFuture(CompletableFuture> future, String msg) {
private void delayUid(String caller, long userId, AtomicInteger cnt, AtomicInteger eqs) {
final String name = Thread.currentThread().getName();
- if (name.startsWith("win-task-")) {
+ if (name.contains("task-")) {
final long ud = TerminalContext.get().getUserId();
log.info("{} delay {}, uid={}", caller, cnt.incrementAndGet(), ud);
if (ud == userId) {
@@ -104,7 +108,7 @@ private void delayUid(String caller, long userId, AtomicInteger cnt, AtomicInteg
}
}
else {
- log.error("bad thread prefix, should start with 'win-task-'");
+ Assertions.fail("bad thread prefix, should start with 'task-'");
}
}
}
diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/errorhandle/DefaultExceptionResolver.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/errorhandle/DefaultExceptionResolver.java
index 5b86cdf32..5c93b4160 100644
--- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/errorhandle/DefaultExceptionResolver.java
+++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/errorhandle/DefaultExceptionResolver.java
@@ -18,6 +18,7 @@
import pro.fessional.mirana.data.R;
import pro.fessional.mirana.pain.CodeException;
import pro.fessional.mirana.pain.HttpStatusException;
+import pro.fessional.mirana.pain.MessageException;
import pro.fessional.mirana.text.JsonTemplate;
import pro.fessional.wings.slardar.context.LocaleZoneIdUtil;
import pro.fessional.wings.slardar.context.TerminalContextException;
@@ -55,8 +56,8 @@ public DefaultExceptionResolver(SimpleResponse defaultResponse, MessageSource me
@Override
protected SimpleResponse resolve(@NotNull Exception exception) {
SimpleResponse response = null;
+ Throwable cause = exception;
try {
- Throwable cause = exception;
for (; response == null && cause != null; cause = cause.getCause()) {
if (cause instanceof HttpStatusException ex) {
response = handle(ex);
@@ -77,10 +78,14 @@ else if (cause instanceof AccessDeniedException ex) {
response = handleAccessDenied(ex);
}
}
+
+ if (response == null) {
+ cause = exception;
+ }
// handler
if (handler != null) {
// use original exception if response is null, otherwise the cause
- response = handler.handle(response == null ? exception : cause, response);
+ response = handler.handle(cause, response);
}
}
catch (Throwable e) {
@@ -92,7 +97,12 @@ else if (cause instanceof AccessDeniedException ex) {
response = defaultResponse;
}
else {
- log.debug("handled exception, response simple", exception);
+ if (cause instanceof MessageException) {
+ log.debug("handled MessageException, response simple", exception);
+ }
+ else {
+ log.info("handled exception, response simple", exception);
+ }
}
return response;
@@ -123,7 +133,7 @@ protected SimpleResponse handle(CodeException cex) {
return new SimpleResponse(defaultResponse.getHttpStatus(), defaultResponse.getContentType(), body);
}
- protected SimpleResponse handleUnauthorized(Exception ex) {
+ protected SimpleResponse handleUnauthorized(Exception ignore) {
final String body = JsonTemplate.obj(obj -> {
obj.putVal("success", false);
String code = AuthnErrorEnum.Unauthorized.getCode();
@@ -133,7 +143,7 @@ protected SimpleResponse handleUnauthorized(Exception ex) {
return new SimpleResponse(HttpStatus.UNAUTHORIZED.value(), defaultResponse.getContentType(), body);
}
- protected SimpleResponse handleAccessDenied(Exception ex) {
+ protected SimpleResponse handleAccessDenied(Exception ignore) {
final String body = JsonTemplate.obj(obj -> {
obj.putVal("success", false);
String code = AuthzErrorEnum.AccessDenied.getCode();
diff --git a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockSecurityBeanConfiguration.java b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockSecurityBeanConfiguration.java
index dd7b18a48..ca63fcd81 100644
--- a/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockSecurityBeanConfiguration.java
+++ b/wings/warlock-shadow/src/main/java/pro/fessional/wings/warlock/spring/bean/WarlockSecurityBeanConfiguration.java
@@ -10,6 +10,7 @@
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.servlet.LocaleResolver;
@@ -274,6 +275,7 @@ public DefaultUserDetailsCombo defaultUserDetailsCombo(WarlockSecurityProp prop)
@Bean
@ConditionalWingsEnabled
+ @Order(WingsOrdered.Lv3Service + 10)
public AuthZonePermChecker authZonePermChecker(WarlockSecurityProp prop) {
log.info("WarlockShadow spring-bean authZonePermChecker");
final AuthZonePermChecker bean = new AuthZonePermChecker();
@@ -283,6 +285,7 @@ public AuthZonePermChecker authZonePermChecker(WarlockSecurityProp prop) {
@Bean
@ConditionalWingsEnabled
+ @Order(WingsOrdered.Lv3Service + 20)
public AuthAppPermChecker authAppPermChecker(@Value("${spring.application.name:wings-default}") String appName, WarlockSecurityProp prop) {
log.info("WarlockShadow spring-bean authAppPermChecker");
final AuthAppPermChecker bean = new AuthAppPermChecker();
@@ -416,6 +419,7 @@ public WingsAuthPageHandler wingsAuthPageHandler(ObjectProvider implements RuntimeConfService {
public static final String PropHandler = "prop";
public static final String JsonHandler = "json";
@@ -61,7 +61,7 @@ public void putHandler(String type, ConversionService handler) {
@Override
public T getObject(String key, TypeDescriptor type) {
- return selfLazy.getObjectCache(key, type);
+ return thisLazy.getObjectCache(key, type);
}
@Override
@@ -133,10 +133,6 @@ public boolean newObject(String key, Object value, String comment) {
return false;
}
- // cache self-invoke
- @Setter(onMethod_ = {@Autowired, @Lazy})
- protected RuntimeConfServiceImpl selfLazy;
-
@Cacheable
@SuppressWarnings("unchecked")
public T getObjectCache(String key, TypeDescriptor type) {