diff --git a/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/main/java/com/alipay/sofa/boot/actuator/autoconfigure/health/ReadinessAutoConfiguration.java b/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/main/java/com/alipay/sofa/boot/actuator/autoconfigure/health/ReadinessAutoConfiguration.java index c8a9e0d9f..4678ce909 100644 --- a/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/main/java/com/alipay/sofa/boot/actuator/autoconfigure/health/ReadinessAutoConfiguration.java +++ b/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/main/java/com/alipay/sofa/boot/actuator/autoconfigure/health/ReadinessAutoConfiguration.java @@ -21,10 +21,13 @@ import com.alipay.sofa.boot.actuator.health.ReadinessCheckCallbackProcessor; import com.alipay.sofa.boot.actuator.health.ReadinessCheckListener; import com.alipay.sofa.boot.actuator.health.ReadinessEndpoint; +import com.alipay.sofa.boot.autoconfigure.condition.OnVirtualThreadStartupAvailableCondition; +import com.alipay.sofa.boot.autoconfigure.condition.OnVirtualThreadStartupDisableCondition; import com.alipay.sofa.boot.constant.SofaBootConstants; import com.alipay.sofa.boot.log.SofaBootLoggerFactory; import com.alipay.sofa.common.thread.NamedThreadFactory; import com.alipay.sofa.common.thread.SofaThreadPoolExecutor; +import com.alipay.sofa.common.thread.virtual.SofaVirtualThreadFactory; import org.slf4j.Logger; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -32,7 +35,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import java.util.concurrent.ExecutorService; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -57,7 +62,7 @@ public class ReadinessAutoConfiguration { public ReadinessCheckListener readinessCheckListener(HealthCheckerProcessor healthCheckerProcessor, HealthIndicatorProcessor healthIndicatorProcessor, ReadinessCheckCallbackProcessor afterReadinessCheckCallbackProcessor, - ThreadPoolExecutor readinessHealthCheckExecutor, + ExecutorService readinessHealthCheckExecutor, HealthProperties healthCheckProperties) { ReadinessCheckListener readinessCheckListener = new ReadinessCheckListener( healthCheckerProcessor, healthIndicatorProcessor, afterReadinessCheckCallbackProcessor); @@ -76,7 +81,7 @@ public ReadinessCheckListener readinessCheckListener(HealthCheckerProcessor heal @Bean @ConditionalOnMissingBean public HealthCheckerProcessor healthCheckerProcessor(HealthProperties healthCheckProperties, - ThreadPoolExecutor readinessHealthCheckExecutor) { + ExecutorService readinessHealthCheckExecutor) { HealthCheckerProcessor healthCheckerProcessor = new HealthCheckerProcessor(); healthCheckerProcessor.setHealthCheckExecutor(readinessHealthCheckExecutor); healthCheckerProcessor.setParallelCheck(healthCheckProperties.isParallelCheck()); @@ -92,7 +97,7 @@ public HealthCheckerProcessor healthCheckerProcessor(HealthProperties healthChec @Bean @ConditionalOnMissingBean public HealthIndicatorProcessor healthIndicatorProcessor(HealthProperties healthCheckProperties, - ThreadPoolExecutor readinessHealthCheckExecutor) { + ExecutorService readinessHealthCheckExecutor) { HealthIndicatorProcessor healthIndicatorProcessor = new HealthIndicatorProcessor(); healthIndicatorProcessor.setHealthCheckExecutor(readinessHealthCheckExecutor); healthIndicatorProcessor.initExcludedIndicators(healthCheckProperties @@ -115,7 +120,8 @@ public ReadinessCheckCallbackProcessor afterReadinessCheckCallbackProcessor() { @Bean(name = ReadinessCheckListener.READINESS_HEALTH_CHECK_EXECUTOR_BEAN_NAME) @ConditionalOnMissingBean(name = ReadinessCheckListener.READINESS_HEALTH_CHECK_EXECUTOR_BEAN_NAME) - public ThreadPoolExecutor readinessHealthCheckExecutor(HealthProperties properties) { + @Conditional(OnVirtualThreadStartupDisableCondition.class) + public ExecutorService readinessHealthCheckExecutor(HealthProperties properties) { int threadPoolSize; if (properties.isParallelCheck()) { threadPoolSize = SofaBootConstants.CPU_CORE * 5; @@ -129,4 +135,12 @@ public ThreadPoolExecutor readinessHealthCheckExecutor(HealthProperties properti new ThreadPoolExecutor.CallerRunsPolicy(), "health-check", SofaBootConstants.SOFA_BOOT_SPACE_NAME); } + + @Bean(name = ReadinessCheckListener.READINESS_HEALTH_CHECK_EXECUTOR_BEAN_NAME) + @ConditionalOnMissingBean(name = ReadinessCheckListener.READINESS_HEALTH_CHECK_EXECUTOR_BEAN_NAME) + @Conditional(OnVirtualThreadStartupAvailableCondition.class) + public ExecutorService readinessHealthCheckVirtualExecutor() { + LOGGER.info("Create health-check virtual executor service"); + return SofaVirtualThreadFactory.ofExecutorService("health-check"); + } } diff --git a/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/test/java/com/alipay/sofa/boot/actuator/autoconfigure/health/ReadinessAutoConfigurationTests.java b/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/test/java/com/alipay/sofa/boot/actuator/autoconfigure/health/ReadinessAutoConfigurationTests.java index 6ac6b8b5e..caf3ada77 100644 --- a/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/test/java/com/alipay/sofa/boot/actuator/autoconfigure/health/ReadinessAutoConfigurationTests.java +++ b/sofa-boot-project/sofa-boot-actuator-autoconfigure/src/test/java/com/alipay/sofa/boot/actuator/autoconfigure/health/ReadinessAutoConfigurationTests.java @@ -27,10 +27,13 @@ import com.alipay.sofa.boot.isle.ApplicationRuntimeModel; import com.alipay.sofa.runtime.spi.component.SofaRuntimeContext; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import static org.assertj.core.api.Assertions.assertThat; @@ -177,4 +180,26 @@ void runWhenNotHaveRuntimeConfigurationWithoutBeans() { .withClassLoader(new FilteredClassLoader(SofaRuntimeContext.class)) .run((context) -> assertThat(context).doesNotHaveBean(ComponentHealthChecker.class)); } + + @Test + public void useReadinessHealthCheckExecutor() { + this.contextRunner + .run((context) -> { + ExecutorService threadPoolExecutor = context.getBean(ReadinessCheckListener.READINESS_HEALTH_CHECK_EXECUTOR_BEAN_NAME, + ExecutorService.class); + assertThat(threadPoolExecutor).isInstanceOf(ThreadPoolExecutor.class); + }); + } + + @Test + @EnabledOnJre(JRE.JAVA_21) + public void useReadinessHealthCheckVirtualExecutor() { + this.contextRunner + .withPropertyValues("sofa.boot.startup.threads.virtual.enabled=true") + .run((context) -> { + ExecutorService threadPoolExecutor = context.getBean(ReadinessCheckListener.READINESS_HEALTH_CHECK_EXECUTOR_BEAN_NAME, + ExecutorService.class); + assertThat(threadPoolExecutor).isNotInstanceOf(ThreadPoolExecutor.class); + }); + } } diff --git a/sofa-boot-project/sofa-boot-actuator/src/main/java/com/alipay/sofa/boot/actuator/health/ReadinessCheckListener.java b/sofa-boot-project/sofa-boot-actuator/src/main/java/com/alipay/sofa/boot/actuator/health/ReadinessCheckListener.java index 07fc48957..fe6d7481f 100644 --- a/sofa-boot-project/sofa-boot-actuator/src/main/java/com/alipay/sofa/boot/actuator/health/ReadinessCheckListener.java +++ b/sofa-boot-project/sofa-boot-actuator/src/main/java/com/alipay/sofa/boot/actuator/health/ReadinessCheckListener.java @@ -42,7 +42,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; @@ -148,7 +148,7 @@ public class ReadinessCheckListener implements ApplicationContextAware, Ordered, private boolean throwExceptionWhenHealthCheckFailed = false; - private ThreadPoolExecutor healthCheckExecutor; + private ExecutorService executorService; public ReadinessCheckListener(HealthCheckerProcessor healthCheckerProcessor, HealthIndicatorProcessor healthIndicatorProcessor, @@ -232,7 +232,7 @@ public void onContextRefreshedEvent(ContextRefreshedEvent event) { if (startupReporter != null) { startupReporter.addCommonStartupStat(stat); } - healthCheckExecutor.shutdown(); + executorService.shutdown(); } } @@ -454,8 +454,8 @@ public ReadinessState getReadinessState() { return readinessState; } - public void setHealthCheckExecutor(ThreadPoolExecutor healthCheckExecutor) { - this.healthCheckExecutor = healthCheckExecutor; + public void setHealthCheckExecutor(ExecutorService executorService) { + this.executorService = executorService; } public static class ManualReadinessCallbackResult { diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/condition/OnVirtualThreadStartupAvailableCondition.java b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/condition/OnVirtualThreadStartupAvailableCondition.java new file mode 100644 index 000000000..dfde779cb --- /dev/null +++ b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/condition/OnVirtualThreadStartupAvailableCondition.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alipay.sofa.boot.autoconfigure.condition; + +import org.springframework.boot.autoconfigure.condition.AllNestedConditions; +import org.springframework.boot.autoconfigure.condition.ConditionalOnJava; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.system.JavaVersion; + +/** + * Condition for startup SOFABoot on virtual thread. + * + * @author huzijie + * @version OnVirtualThreadStartupAvailableCondition.java, v 0.1 2023年12月05日 4:51 PM huzijie Exp $ + */ +public class OnVirtualThreadStartupAvailableCondition extends AllNestedConditions { + public OnVirtualThreadStartupAvailableCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnJava(value = JavaVersion.TWENTY_ONE) + static class JdkVersionAvailable { + + } + + @ConditionalOnProperty(value = "sofa.boot.startup.threads.virtual.enabled", havingValue = "true") + static class IslePropertyAvailable { + + } +} diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/condition/OnVirtualThreadStartupDisableCondition.java b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/condition/OnVirtualThreadStartupDisableCondition.java new file mode 100644 index 000000000..49d82ccd0 --- /dev/null +++ b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/condition/OnVirtualThreadStartupDisableCondition.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alipay.sofa.boot.autoconfigure.condition; + +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; +import org.springframework.boot.autoconfigure.condition.ConditionalOnJava; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.system.JavaVersion; + +/** + * Condition for not startup SOFABoot on virtual thread. + * + * @author huzijie + * @version StartupOnVirtualThreadDisableCondition.java, v 0.1 2023年12月05日 4:51 PM huzijie Exp $ + */ +public class OnVirtualThreadStartupDisableCondition extends AnyNestedCondition { + public OnVirtualThreadStartupDisableCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnJava(value = JavaVersion.TWENTY_ONE, range = ConditionalOnJava.Range.OLDER_THAN) + static class JdkVersionAvailable { + + } + + @ConditionalOnProperty(value = "sofa.boot.startup.threads.virtual.enabled", havingValue = "false", matchIfMissing = true) + static class IslePropertyAvailable { + + } +} diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/isle/SofaModuleAutoConfiguration.java b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/isle/SofaModuleAutoConfiguration.java index 2117eefd2..576a19d40 100644 --- a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/isle/SofaModuleAutoConfiguration.java +++ b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/isle/SofaModuleAutoConfiguration.java @@ -16,6 +16,8 @@ */ package com.alipay.sofa.boot.autoconfigure.isle; +import com.alipay.sofa.boot.autoconfigure.condition.OnVirtualThreadStartupAvailableCondition; +import com.alipay.sofa.boot.autoconfigure.condition.OnVirtualThreadStartupDisableCondition; import com.alipay.sofa.boot.constant.SofaBootConstants; import com.alipay.sofa.boot.context.ContextRefreshInterceptor; import com.alipay.sofa.boot.context.processor.SofaPostProcessorShareFilter; @@ -37,6 +39,7 @@ import com.alipay.sofa.boot.log.SofaBootLoggerFactory; import com.alipay.sofa.common.thread.NamedThreadFactory; import com.alipay.sofa.common.thread.SofaThreadPoolExecutor; +import com.alipay.sofa.common.thread.virtual.SofaVirtualThreadFactory; import org.slf4j.Logger; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -51,6 +54,7 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -152,7 +156,8 @@ public SpringContextLoader sofaDynamicSpringContextLoader(SofaModuleProperties s @Bean(SpringContextInstallStage.SOFA_MODULE_REFRESH_EXECUTOR_BEAN_NAME) @ConditionalOnMissingBean(name = SpringContextInstallStage.SOFA_MODULE_REFRESH_EXECUTOR_BEAN_NAME) @ConditionalOnProperty(value = "sofa.boot.isle.moduleStartUpParallel", havingValue = "true", matchIfMissing = true) - public Supplier sofaModuleRefreshExecutor(SofaModuleProperties sofaModuleProperties) { + @Conditional(OnVirtualThreadStartupDisableCondition.class) + public Supplier sofaModuleRefreshExecutor(SofaModuleProperties sofaModuleProperties) { int coreSize = (int) (SofaBootConstants.CPU_CORE * sofaModuleProperties.getParallelRefreshPoolSizeFactor()); long taskTimeout = sofaModuleProperties.getParallelRefreshTimeout(); long checkPeriod = sofaModuleProperties.getParallelRefreshCheckPeriod(); @@ -167,6 +172,17 @@ public Supplier sofaModuleRefreshExecutor(SofaModuleProperti TimeUnit.SECONDS); } + @Bean(SpringContextInstallStage.SOFA_MODULE_REFRESH_EXECUTOR_BEAN_NAME) + @ConditionalOnMissingBean(name = SpringContextInstallStage.SOFA_MODULE_REFRESH_EXECUTOR_BEAN_NAME) + @ConditionalOnProperty(value = "sofa.boot.isle.moduleStartUpParallel", havingValue = "true", matchIfMissing = true) + @Conditional(OnVirtualThreadStartupAvailableCondition.class) + public Supplier sofaModuleRefreshVirtualExecutor() { + return () -> { + LOGGER.info("Create SOFA module refresh virtual executor service"); + return SofaVirtualThreadFactory.ofExecutorService("sofa-module-refresh"); + }; + } + @Bean @ConditionalOnMissingBean public SofaModuleProfileChecker sofaModuleProfileChecker(SofaModuleProperties sofaModuleProperties) { diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/runtime/SofaRuntimeAutoConfiguration.java b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/runtime/SofaRuntimeAutoConfiguration.java index 6ff7e86c7..4227b2ec9 100644 --- a/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/runtime/SofaRuntimeAutoConfiguration.java +++ b/sofa-boot-project/sofa-boot-autoconfigure/src/main/java/com/alipay/sofa/boot/autoconfigure/runtime/SofaRuntimeAutoConfiguration.java @@ -17,10 +17,13 @@ package com.alipay.sofa.boot.autoconfigure.runtime; import com.alipay.sofa.boot.autoconfigure.condition.ConditionalOnSwitch; +import com.alipay.sofa.boot.autoconfigure.condition.OnVirtualThreadStartupAvailableCondition; +import com.alipay.sofa.boot.autoconfigure.condition.OnVirtualThreadStartupDisableCondition; import com.alipay.sofa.boot.constant.SofaBootConstants; import com.alipay.sofa.boot.log.SofaBootLoggerFactory; import com.alipay.sofa.common.thread.NamedThreadFactory; import com.alipay.sofa.common.thread.SofaThreadPoolExecutor; +import com.alipay.sofa.common.thread.virtual.SofaVirtualThreadFactory; import com.alipay.sofa.runtime.api.client.ReferenceClient; import com.alipay.sofa.runtime.api.client.ServiceClient; import com.alipay.sofa.runtime.async.AsyncInitMethodManager; @@ -54,12 +57,14 @@ import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.util.Assert; import java.util.HashSet; +import java.util.concurrent.ExecutorService; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -167,7 +172,8 @@ public static AsyncInitMethodManager asyncInitMethodManager() { @Bean(AsyncInitMethodManager.ASYNC_INIT_METHOD_EXECUTOR_BEAN_NAME) @ConditionalOnMissingBean(name = AsyncInitMethodManager.ASYNC_INIT_METHOD_EXECUTOR_BEAN_NAME) - public Supplier asyncInitMethodExecutor(SofaRuntimeProperties sofaRuntimeProperties) { + @Conditional(OnVirtualThreadStartupDisableCondition.class) + public Supplier asyncInitMethodExecutor(SofaRuntimeProperties sofaRuntimeProperties) { return ()-> { int coreSize = sofaRuntimeProperties.getAsyncInitExecutorCoreSize(); int maxSize = sofaRuntimeProperties.getAsyncInitExecutorMaxSize(); @@ -183,6 +189,16 @@ public Supplier asyncInitMethodExecutor(SofaRuntimePropertie }; } + @Bean(AsyncInitMethodManager.ASYNC_INIT_METHOD_EXECUTOR_BEAN_NAME) + @ConditionalOnMissingBean(name = AsyncInitMethodManager.ASYNC_INIT_METHOD_EXECUTOR_BEAN_NAME) + @Conditional(OnVirtualThreadStartupAvailableCondition.class) + public Supplier asyncInitMethodVirtualExecutor() { + return ()-> { + LOGGER.info("create async-init-bean virtual executor service"); + return SofaVirtualThreadFactory.ofExecutorService("async-init-bean"); + }; + } + @Bean @ConditionalOnMissingBean public static AsyncProxyBeanPostProcessor asyncProxyBeanPostProcessor(AsyncInitMethodManager asyncInitMethodManager) { diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/condition/OnVirtualThreadStartupConditionTests.java b/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/condition/OnVirtualThreadStartupConditionTests.java new file mode 100644 index 000000000..5f55ed2df --- /dev/null +++ b/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/condition/OnVirtualThreadStartupConditionTests.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alipay.sofa.boot.autoconfigure.condition; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnJre; +import org.junit.jupiter.api.condition.JRE; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.StandardEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link OnVirtualThreadStartupDisableCondition} and {@link OnVirtualThreadStartupAvailableCondition}. + * + * @author huzijie + * @version OnVirtualThreadStartupConditionTests.java, v 0.1 2023年12月05日 5:04 PM huzijie Exp $ + */ +public class OnVirtualThreadStartupConditionTests { + + private ConfigurableApplicationContext context; + + private final ConfigurableEnvironment environment = new StandardEnvironment(); + + @Test + @EnabledOnJre(JRE.JAVA_17) + void checkJdk17Environment() { + load(OnVirtualThreadStartupConditionTests.OnVirtualThreadStartupConfiguration.class); + assertThat(this.context.containsBean("sampleA")).isTrue(); + assertThat(this.context.containsBean("sampleB")).isFalse(); + } + + @Test + @EnabledOnJre(JRE.JAVA_17) + void checkJdk17EnvironmentEnableProperty() { + load(OnVirtualThreadStartupConditionTests.OnVirtualThreadStartupConfiguration.class, + "sofa.boot.startup.threads.virtual.enabled=true"); + assertThat(this.context.containsBean("sampleA")).isTrue(); + assertThat(this.context.containsBean("sampleB")).isFalse(); + } + + @Test + @EnabledOnJre(JRE.JAVA_21) + void checkJdk21Environment() { + load(OnVirtualThreadStartupConditionTests.OnVirtualThreadStartupConfiguration.class); + assertThat(this.context.containsBean("sampleA")).isTrue(); + assertThat(this.context.containsBean("sampleB")).isFalse(); + } + + @Test + @EnabledOnJre(JRE.JAVA_21) + void checkJdk21EnvironmentEnableProperty() { + load(OnVirtualThreadStartupConditionTests.OnVirtualThreadStartupConfiguration.class, + "sofa.boot.startup.threads.virtual.enabled=true"); + assertThat(this.context.containsBean("sampleA")).isFalse(); + assertThat(this.context.containsBean("sampleB")).isTrue(); + } + + private void load(Class config, String... environment) { + TestPropertyValues.of(environment).and("spring.application.name=test") + .applyTo(this.environment); + this.context = new SpringApplicationBuilder(config).environment(this.environment) + .web(WebApplicationType.NONE).run(); + } + + @Configuration + static class OnVirtualThreadStartupConfiguration { + + @Bean + @Conditional(OnVirtualThreadStartupDisableCondition.class) + public String sampleA() { + return "sample"; + } + + @Bean + @Conditional(OnVirtualThreadStartupAvailableCondition.class) + public String sampleB() { + return "sample"; + } + } +} diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/isle/SofaModuleAutoConfigurationTests.java b/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/isle/SofaModuleAutoConfigurationTests.java index 8ed49b60f..d1aa6698a 100644 --- a/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/isle/SofaModuleAutoConfigurationTests.java +++ b/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/isle/SofaModuleAutoConfigurationTests.java @@ -31,10 +31,14 @@ import com.alipay.sofa.boot.isle.stage.SpringContextInstallStage; import com.alipay.sofa.common.thread.SofaThreadPoolExecutor; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadPoolExecutor; import java.util.function.Supplier; import static org.assertj.core.api.Assertions.assertThat; @@ -183,4 +187,16 @@ void customModelCreatingStage() { assertThat(modelCreatingStage.isAllowModuleOverriding()).isTrue(); }); } + + @Test + @EnabledOnJre(JRE.JAVA_21) + public void useSofaModuleRefreshVirtualExecutor() { + this.contextRunner + .withPropertyValues("sofa.boot.startup.threads.virtual.enabled=true") + .run((context) -> { + ExecutorService threadPoolExecutor = (ExecutorService) context.getBean(Supplier.class, + SpringContextInstallStage.SOFA_MODULE_REFRESH_EXECUTOR_BEAN_NAME).get(); + assertThat(threadPoolExecutor).isNotInstanceOf(ThreadPoolExecutor.class); + }); + } } diff --git a/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/runtime/SofaRuntimeAutoConfigurationTests.java b/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/runtime/SofaRuntimeAutoConfigurationTests.java index bb6f6d31e..c6ab92b71 100644 --- a/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/runtime/SofaRuntimeAutoConfigurationTests.java +++ b/sofa-boot-project/sofa-boot-autoconfigure/src/test/java/com/alipay/sofa/boot/autoconfigure/runtime/SofaRuntimeAutoConfigurationTests.java @@ -28,10 +28,13 @@ import com.alipay.sofa.runtime.spring.RuntimeContextBeanFactoryPostProcessor; import com.alipay.sofa.runtime.spring.ServiceBeanFactoryPostProcessor; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.function.Supplier; @@ -119,10 +122,23 @@ public void customAsyncInitMethodManager() { .withPropertyValues("sofa.boot.runtime.asyncInitExecutorCoreSize=10") .withPropertyValues("sofa.boot.runtime.asyncInitExecutorMaxSize=10") .run((context) -> { - ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) context.getBean(Supplier.class, + ExecutorService threadPoolExecutor = (ExecutorService) context.getBean(Supplier.class, AsyncInitMethodManager.ASYNC_INIT_METHOD_EXECUTOR_BEAN_NAME).get(); - assertThat(threadPoolExecutor.getCorePoolSize()).isEqualTo(10); - assertThat(threadPoolExecutor.getMaximumPoolSize()).isEqualTo(10); + assertThat(threadPoolExecutor).isInstanceOf(ThreadPoolExecutor.class); + assertThat(((ThreadPoolExecutor) threadPoolExecutor).getCorePoolSize()).isEqualTo(10); + assertThat(((ThreadPoolExecutor) threadPoolExecutor).getMaximumPoolSize()).isEqualTo(10); + }); + } + + @Test + @EnabledOnJre(JRE.JAVA_21) + public void useAsyncInitMethodVirtualExecutor() { + this.contextRunner + .withPropertyValues("sofa.boot.startup.threads.virtual.enabled=true") + .run((context) -> { + ExecutorService threadPoolExecutor = (ExecutorService) context.getBean(Supplier.class, + AsyncInitMethodManager.ASYNC_INIT_METHOD_EXECUTOR_BEAN_NAME).get(); + assertThat(threadPoolExecutor).isNotInstanceOf(ThreadPoolExecutor.class); }); } } diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/async/AsyncInitMethodManager.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/async/AsyncInitMethodManager.java index 05b803d18..b93ed6ec6 100644 --- a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/async/AsyncInitMethodManager.java +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/async/AsyncInitMethodManager.java @@ -30,8 +30,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; -import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; @@ -49,7 +49,7 @@ public class AsyncInitMethodManager implements PriorityOrdered, public static final String ASYNC_INIT_METHOD_NAME = "async-init-method-name"; - private final AtomicReference threadPoolExecutorRef = new AtomicReference<>(); + private final AtomicReference executorServiceRef = new AtomicReference<>(); private final Map> asyncInitBeanNameMap = new ConcurrentHashMap<>(); @@ -77,20 +77,20 @@ public void setApplicationContext(ApplicationContext applicationContext) throws } public void submitTask(Runnable runnable) { - if (threadPoolExecutorRef.get() == null) { - ThreadPoolExecutor threadPoolExecutor = createAsyncExecutor(); - boolean success = threadPoolExecutorRef.compareAndSet(null, threadPoolExecutor); + if (executorServiceRef.get() == null) { + ExecutorService executorService = createAsyncExecutorService(); + boolean success = executorServiceRef.compareAndSet(null, executorService); if (!success) { - threadPoolExecutor.shutdown(); + executorService.shutdown(); } } - Future future = threadPoolExecutorRef.get().submit(runnable); + Future future = executorServiceRef.get().submit(runnable); futures.add(future); } - private ThreadPoolExecutor createAsyncExecutor() { - return (ThreadPoolExecutor) applicationContext.getBean( - ASYNC_INIT_METHOD_EXECUTOR_BEAN_NAME, Supplier.class).get(); + private ExecutorService createAsyncExecutorService() { + return (ExecutorService) applicationContext.getBean(ASYNC_INIT_METHOD_EXECUTOR_BEAN_NAME, + Supplier.class).get(); } void ensureAsyncTasksFinish() { @@ -105,9 +105,9 @@ void ensureAsyncTasksFinish() { startUpFinish = true; futures.clear(); asyncInitBeanNameMap.clear(); - if (threadPoolExecutorRef.get() != null) { - threadPoolExecutorRef.get().shutdown(); - threadPoolExecutorRef.set(null); + if (executorServiceRef.get() != null) { + executorServiceRef.get().shutdown(); + executorServiceRef.set(null); } } diff --git a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ServiceBeanFactoryPostProcessor.java b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ServiceBeanFactoryPostProcessor.java index 4ff673e60..b4e9e1d83 100644 --- a/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ServiceBeanFactoryPostProcessor.java +++ b/sofa-boot-project/sofa-boot-core/runtime-sofa-boot/src/main/java/com/alipay/sofa/runtime/spring/ServiceBeanFactoryPostProcessor.java @@ -35,7 +35,7 @@ import com.alipay.sofa.runtime.spi.service.BindingConverter; import com.alipay.sofa.runtime.spi.service.BindingConverterContext; import com.alipay.sofa.runtime.spi.service.BindingConverterFactory; -import com.alipay.sofa.runtime.spring.bean.LocalVariableTableParameterNameDiscoverer; +import com.alipay.sofa.boot.spring.parameter.LocalVariableTableParameterNameDiscoverer; import com.alipay.sofa.runtime.spring.bean.SofaBeanNameGenerator; import com.alipay.sofa.runtime.spring.bean.SofaParameterNameDiscoverer; import com.alipay.sofa.runtime.spring.factory.ReferenceFactoryBean; diff --git a/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/spring/parameter/LocalVariableTableParameterNameDiscoverer.java b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/spring/parameter/LocalVariableTableParameterNameDiscoverer.java new file mode 100644 index 000000000..4d4867aa3 --- /dev/null +++ b/sofa-boot-project/sofa-boot/src/main/java/com/alipay/sofa/boot/spring/parameter/LocalVariableTableParameterNameDiscoverer.java @@ -0,0 +1,270 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alipay.sofa.boot.spring.parameter; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.asm.ClassReader; +import org.springframework.asm.ClassVisitor; +import org.springframework.asm.Label; +import org.springframework.asm.MethodVisitor; +import org.springframework.asm.Opcodes; +import org.springframework.asm.SpringAsmInfo; +import org.springframework.asm.Type; +import org.springframework.core.BridgeMethodResolver; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Deprecate in spring, fork codes for compatible + */ +@Deprecated +public class LocalVariableTableParameterNameDiscoverer implements ParameterNameDiscoverer { + + private static final Log logger = LogFactory + .getLog(LocalVariableTableParameterNameDiscoverer.class); + + // marker object for classes that do not have any debug info + private static final Map NO_DEBUG_INFO_MAP = Collections + .emptyMap(); + + // the cache uses a nested index (value is a map) to keep the top level cache relatively small in size + private final Map, Map> parameterNamesCache = new ConcurrentHashMap<>( + 32); + + @Override + @Nullable + public String[] getParameterNames(Method method) { + Method originalMethod = BridgeMethodResolver.findBridgedMethod(method); + return doGetParameterNames(originalMethod); + } + + @Override + @Nullable + public String[] getParameterNames(Constructor ctor) { + return doGetParameterNames(ctor); + } + + @Nullable + private String[] doGetParameterNames(Executable executable) { + Class declaringClass = executable.getDeclaringClass(); + Map map = this.parameterNamesCache.computeIfAbsent(declaringClass, this::inspectClass); + return (map != NO_DEBUG_INFO_MAP ? map.get(executable) : null); + } + + /** + * Inspects the target class. + *

Exceptions will be logged, and a marker map returned to indicate the + * lack of debug information. + */ + private Map inspectClass(Class clazz) { + InputStream is = clazz.getResourceAsStream(ClassUtils.getClassFileName(clazz)); + if (is == null) { + // We couldn't load the class file, which is not fatal as it + // simply means this method of discovering parameter names won't work. + if (logger.isDebugEnabled()) { + logger.debug("Cannot find '.class' file for class [" + clazz + + "] - unable to determine constructor/method parameter names"); + } + return NO_DEBUG_INFO_MAP; + } + // We cannot use try-with-resources here for the InputStream, since we have + // custom handling of the close() method in a finally-block. + try { + ClassReader classReader = new ClassReader(is); + Map map = new ConcurrentHashMap<>(32); + classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0); + if (logger.isWarnEnabled()) { + logger + .warn("Using deprecated '-debug' fallback for parameter name resolution. Compile the " + + "affected code with '-parameters' instead or avoid its introspection: " + + clazz.getName()); + } + return map; + } catch (IOException ex) { + if (logger.isDebugEnabled()) { + logger.debug("Exception thrown while reading '.class' file for class [" + clazz + + "] - unable to determine constructor/method parameter names", ex); + } + } catch (IllegalArgumentException ex) { + if (logger.isDebugEnabled()) { + logger + .debug( + "ASM ClassReader failed to parse class file [" + + clazz + + "], probably due to a new Java class file version that isn't supported yet " + + "- unable to determine constructor/method parameter names", ex); + } + } finally { + try { + is.close(); + } catch (IOException ex) { + // ignore + } + } + return NO_DEBUG_INFO_MAP; + } + + /** + * Helper class that inspects all methods and constructors and then + * attempts to find the parameter names for the given {@link Executable}. + */ + private static class ParameterNameDiscoveringVisitor extends ClassVisitor { + + private static final String STATIC_CLASS_INIT = ""; + + private final Class clazz; + + private final Map executableMap; + + public ParameterNameDiscoveringVisitor(Class clazz, + Map executableMap) { + super(SpringAsmInfo.ASM_VERSION); + this.clazz = clazz; + this.executableMap = executableMap; + } + + @Override + @Nullable + public MethodVisitor visitMethod(int access, String name, String desc, String signature, + String[] exceptions) { + // exclude synthetic + bridged && static class initialization + if (!isSyntheticOrBridged(access) && !STATIC_CLASS_INIT.equals(name)) { + return new LocalVariableTableVisitor(this.clazz, this.executableMap, name, desc, + isStatic(access)); + } + return null; + } + + private static boolean isSyntheticOrBridged(int access) { + return (((access & Opcodes.ACC_SYNTHETIC) | (access & Opcodes.ACC_BRIDGE)) > 0); + } + + private static boolean isStatic(int access) { + return ((access & Opcodes.ACC_STATIC) > 0); + } + } + + private static class LocalVariableTableVisitor extends MethodVisitor { + + private static final String CONSTRUCTOR = ""; + + private final Class clazz; + + private final Map executableMap; + + private final String name; + + private final Type[] args; + + private final String[] parameterNames; + + private final boolean isStatic; + + private boolean hasLvtInfo = false; + + /* + * The nth entry contains the slot index of the LVT table entry holding the + * argument name for the nth parameter. + */ + private final int[] lvtSlotIndex; + + public LocalVariableTableVisitor(Class clazz, Map map, + String name, String desc, boolean isStatic) { + super(SpringAsmInfo.ASM_VERSION); + this.clazz = clazz; + this.executableMap = map; + this.name = name; + this.args = Type.getArgumentTypes(desc); + this.parameterNames = new String[this.args.length]; + this.isStatic = isStatic; + this.lvtSlotIndex = computeLvtSlotIndices(isStatic, this.args); + } + + @Override + public void visitLocalVariable(String name, String description, String signature, + Label start, Label end, int index) { + this.hasLvtInfo = true; + for (int i = 0; i < this.lvtSlotIndex.length; i++) { + if (this.lvtSlotIndex[i] == index) { + this.parameterNames[i] = name; + } + } + } + + @Override + public void visitEnd() { + if (this.hasLvtInfo || (this.isStatic && this.parameterNames.length == 0)) { + // visitLocalVariable will never be called for static no args methods + // which doesn't use any local variables. + // This means that hasLvtInfo could be false for that kind of methods + // even if the class has local variable info. + this.executableMap.put(resolveExecutable(), this.parameterNames); + } + } + + private Executable resolveExecutable() { + ClassLoader loader = this.clazz.getClassLoader(); + Class[] argTypes = new Class[this.args.length]; + for (int i = 0; i < this.args.length; i++) { + argTypes[i] = ClassUtils.resolveClassName(this.args[i].getClassName(), loader); + } + try { + if (CONSTRUCTOR.equals(this.name)) { + return this.clazz.getDeclaredConstructor(argTypes); + } + return this.clazz.getDeclaredMethod(this.name, argTypes); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException( + "Method [" + + this.name + + "] was discovered in the .class file but cannot be resolved in the class object", + ex); + } + } + + private static int[] computeLvtSlotIndices(boolean isStatic, Type[] paramTypes) { + int[] lvtIndex = new int[paramTypes.length]; + int nextIndex = (isStatic ? 0 : 1); + for (int i = 0; i < paramTypes.length; i++) { + lvtIndex[i] = nextIndex; + if (isWideType(paramTypes[i])) { + nextIndex += 2; + } else { + nextIndex++; + } + } + return lvtIndex; + } + + private static boolean isWideType(Type aType) { + // float is not a wide type + return (aType == Type.LONG_TYPE || aType == Type.DOUBLE_TYPE); + } + } + +} diff --git a/sofa-boot-project/sofaboot-dependencies/pom.xml b/sofa-boot-project/sofaboot-dependencies/pom.xml index 6bc92f82e..ede76043f 100644 --- a/sofa-boot-project/sofaboot-dependencies/pom.xml +++ b/sofa-boot-project/sofaboot-dependencies/pom.xml @@ -25,7 +25,7 @@ 6.1.8 4.0.0 5.11.0 - 2.0.1 + 2.1.0-SNAPSHOT 1.6.6 3.5.1 3.0.1-SNAPSHOT diff --git a/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/main/java/com/alipay/sofa/smoke/tests/actuator/sample/readiness/SampleHealthChecker.java b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/main/java/com/alipay/sofa/smoke/tests/actuator/sample/readiness/SampleHealthChecker.java new file mode 100644 index 000000000..a058d208d --- /dev/null +++ b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/main/java/com/alipay/sofa/smoke/tests/actuator/sample/readiness/SampleHealthChecker.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alipay.sofa.smoke.tests.actuator.sample.readiness; + +import com.alipay.sofa.boot.actuator.health.HealthChecker; +import org.springframework.boot.actuate.health.Health; + +/** + * @author huzijie + * @version SampleHealthChecker.java, v 0.1 2023年11月24日 4:08 PM huzijie Exp $ + */ +public class SampleHealthChecker implements HealthChecker { + + private final long sleep; + + public SampleHealthChecker(long sleep) { + this.sleep = sleep; + } + + @Override + public Health isHealthy() { + if (sleep > 0) { + try { + Thread.sleep(sleep); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + return Health.up().build(); + } + + @Override + public String getComponentName() { + return "sample"; + } +} diff --git a/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/main/java/com/alipay/sofa/smoke/tests/actuator/sample/readiness/SampleHealthIndicate.java b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/main/java/com/alipay/sofa/smoke/tests/actuator/sample/readiness/SampleHealthIndicate.java new file mode 100644 index 000000000..7d502ab33 --- /dev/null +++ b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/main/java/com/alipay/sofa/smoke/tests/actuator/sample/readiness/SampleHealthIndicate.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alipay.sofa.smoke.tests.actuator.sample.readiness; + +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; + +/** + * @author huzijie + * @version SampleHealthIndicate.java, v 0.1 2023年11月24日 4:08 PM huzijie Exp $ + */ +public class SampleHealthIndicate implements HealthIndicator { + + private final long sleep; + + public SampleHealthIndicate(long sleep) { + this.sleep = sleep; + } + + @Override + public Health health() { + if (sleep > 0) { + try { + Thread.sleep(sleep); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + return Health.up().build(); + } +} diff --git a/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/main/java/com/alipay/sofa/smoke/tests/actuator/sample/readiness/SampleReadinessCheckCallback.java b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/main/java/com/alipay/sofa/smoke/tests/actuator/sample/readiness/SampleReadinessCheckCallback.java new file mode 100644 index 000000000..0786f769b --- /dev/null +++ b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/main/java/com/alipay/sofa/smoke/tests/actuator/sample/readiness/SampleReadinessCheckCallback.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alipay.sofa.smoke.tests.actuator.sample.readiness; + +import com.alipay.sofa.boot.actuator.health.ReadinessCheckCallback; +import org.springframework.boot.actuate.health.Health; +import org.springframework.context.ApplicationContext; + +/** + * @author huzijie + * @version SampleReadinessCheckCallback.java, v 0.1 2023年11月24日 4:09 PM huzijie Exp $ + */ +public class SampleReadinessCheckCallback implements ReadinessCheckCallback { + private final long sleep; + + public SampleReadinessCheckCallback(long sleep) { + this.sleep = sleep; + } + + @Override + public Health onHealthy(ApplicationContext applicationContext) { + return Health.up().build(); + } +} diff --git a/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/test/java/com/alipay/sofa/smoke/tests/actuator/health/ParallelReadinessHealthCheckTests.java b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/test/java/com/alipay/sofa/smoke/tests/actuator/health/ParallelReadinessHealthCheckTests.java new file mode 100644 index 000000000..6e4d9b0b0 --- /dev/null +++ b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/test/java/com/alipay/sofa/smoke/tests/actuator/health/ParallelReadinessHealthCheckTests.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alipay.sofa.smoke.tests.actuator.health; + +import org.springframework.test.context.TestPropertySource; + +/** + * Integration tests for multi components in parallel mode. + * + * @author huzijie + * @version ParallelReadinessHealthCheckTests.java, v 0.1 2023年11月27日 11:30 AM huzijie Exp $ + */ +@TestPropertySource(properties = { "sofa.boot.actuator.health.parallelCheck=true", "bean.count=100" }) +public class ParallelReadinessHealthCheckTests extends ReadinessHealthCheckTestBase { +} diff --git a/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/test/java/com/alipay/sofa/smoke/tests/actuator/health/ReadinessHealthCheckTestBase.java b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/test/java/com/alipay/sofa/smoke/tests/actuator/health/ReadinessHealthCheckTestBase.java new file mode 100644 index 000000000..a95f83f84 --- /dev/null +++ b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/test/java/com/alipay/sofa/smoke/tests/actuator/health/ReadinessHealthCheckTestBase.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alipay.sofa.smoke.tests.actuator.health; + +import com.alipay.sofa.boot.actuator.health.ReadinessCheckListener; +import com.alipay.sofa.boot.context.processor.UnshareSofaPostProcessor; +import com.alipay.sofa.smoke.tests.actuator.ActuatorSofaBootApplication; +import com.alipay.sofa.smoke.tests.actuator.sample.readiness.SampleHealthChecker; +import com.alipay.sofa.smoke.tests.actuator.sample.readiness.SampleHealthIndicate; +import com.alipay.sofa.smoke.tests.actuator.sample.readiness.SampleReadinessCheckCallback; +import org.junit.jupiter.api.Test; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.ConstructorArgumentValues; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.beans.factory.support.GenericBeanDefinition; +import org.springframework.boot.availability.ReadinessState; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.EnvironmentAware; +import org.springframework.context.annotation.Import; +import org.springframework.core.env.Environment; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Random; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Base integration tests for multi components. + * + * @author huzijie + * @version ReadinessHealthCheckTestBase.java, v 0.1 2023年11月24日 4:06 PM huzijie Exp $ + */ +@SpringBootTest(classes = ActuatorSofaBootApplication.class) +@Import(ReadinessHealthCheckTestBase.ReadinessBeanDefinitionRegistryPostProcessor.class) +public class ReadinessHealthCheckTestBase { + + @Value("${bean.count}") + private int beanCount; + + @Autowired + private ReadinessCheckListener readinessCheckListener; + + @Test + public void checkReadinessResult() { + assertThat(readinessCheckListener.getReadinessState()).isEqualTo( + ReadinessState.ACCEPTING_TRAFFIC); + assertThat(readinessCheckListener.getHealthCheckerDetails().size()).isEqualTo(beanCount); + assertThat(readinessCheckListener.getHealthIndicatorDetails().size()).isEqualTo(beanCount); + assertThat(readinessCheckListener.getHealthCallbackDetails().size()).isEqualTo(beanCount); + } + + @UnshareSofaPostProcessor + static class ReadinessBeanDefinitionRegistryPostProcessor implements + BeanDefinitionRegistryPostProcessor, + EnvironmentAware { + + private int beanCount; + + @Override + public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) + throws BeansException { + if (registry.containsBeanDefinition("sofaModuleHealthChecker")) { + registry.removeBeanDefinition("sofaModuleHealthChecker"); + } + if (registry.containsBeanDefinition("sofaComponentHealthChecker")) { + registry.removeBeanDefinition("sofaComponentHealthChecker"); + } + if (registry.containsBeanDefinition("diskSpaceHealthIndicator")) { + registry.removeBeanDefinition("diskSpaceHealthIndicator"); + } + if (registry.containsBeanDefinition("pingHealthContributor")) { + registry.removeBeanDefinition("pingHealthContributor"); + } + if (registry.containsBeanDefinition("rpcAfterHealthCheckCallback")) { + registry.removeBeanDefinition("rpcAfterHealthCheckCallback"); + } + + registerRandomBeanDefinitions(registry, SampleHealthChecker.class); + registerRandomBeanDefinitions(registry, SampleHealthIndicate.class); + registerRandomBeanDefinitions(registry, SampleReadinessCheckCallback.class); + } + + private void registerRandomBeanDefinitions(BeanDefinitionRegistry registry, Class clazz) { + Set numbers = new HashSet<>(); + Random random = new Random(); + + while (numbers.size() < beanCount) { + int number = random.nextInt(1000); + numbers.add(number); + } + + numbers.forEach(sleep -> register(registry, clazz, sleep)); + } + + private void register(BeanDefinitionRegistry registry, Class clazz, long sleep) { + GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); + beanDefinition.setBeanClass(clazz); + ConstructorArgumentValues values = new ConstructorArgumentValues(); + values.addIndexedArgumentValue(0, sleep); + beanDefinition.setConstructorArgumentValues(values); + registry.registerBeanDefinition(clazz.getSimpleName() + sleep, beanDefinition); + } + + @Override + public void setEnvironment(Environment environment) { + this.beanCount = Integer.parseInt(Objects.requireNonNull(environment + .getProperty("bean.count"))); + } + } + +} diff --git a/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/test/java/com/alipay/sofa/smoke/tests/actuator/health/SerialReadinessHealthCheckTests.java b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/test/java/com/alipay/sofa/smoke/tests/actuator/health/SerialReadinessHealthCheckTests.java new file mode 100644 index 000000000..3c7025f44 --- /dev/null +++ b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/test/java/com/alipay/sofa/smoke/tests/actuator/health/SerialReadinessHealthCheckTests.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alipay.sofa.smoke.tests.actuator.health; + +import org.springframework.test.context.TestPropertySource; + +/** + * Integration tests for multi components in serial mode. + * + * @author huzijie + * @version SerialReadinessHealthCheckTests.java, v 0.1 2023年11月27日 11:29 AM huzijie Exp $ + */ +@TestPropertySource(properties = { "sofa.boot.actuator.health.parallelCheck=false", "bean.count=5" }) +public class SerialReadinessHealthCheckTests extends ReadinessHealthCheckTestBase { +} diff --git a/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/test/java/com/alipay/sofa/smoke/tests/actuator/health/VirtualParallelReadinessHealthCheckTests.java b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/test/java/com/alipay/sofa/smoke/tests/actuator/health/VirtualParallelReadinessHealthCheckTests.java new file mode 100644 index 000000000..3be63a77c --- /dev/null +++ b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-actuator/src/test/java/com/alipay/sofa/smoke/tests/actuator/health/VirtualParallelReadinessHealthCheckTests.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alipay.sofa.smoke.tests.actuator.health; + +import org.springframework.test.context.TestPropertySource; + +/** + * Integration tests for multi components in parallel mode when use virtual threads. + * + * @author huzijie + * @version VirtualParallelReadinessHealthCheckTests.java, v 0.1 2023年11月27日 11:31 AM huzijie Exp $ + */ +@TestPropertySource(properties = { "sofa.boot.startup.threads.virtual.enabled=true", + "bean.count=300" }) +public class VirtualParallelReadinessHealthCheckTests extends ParallelReadinessHealthCheckTests { +} diff --git a/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-isle/src/test/java/com/alipay/sofa/smoke/tests/isle/ParallelSpringContextInstallStageTests.java b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-isle/src/test/java/com/alipay/sofa/smoke/tests/isle/ParallelSpringContextInstallStageTests.java index ef1ece14e..bd1d92431 100644 --- a/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-isle/src/test/java/com/alipay/sofa/smoke/tests/isle/ParallelSpringContextInstallStageTests.java +++ b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-isle/src/test/java/com/alipay/sofa/smoke/tests/isle/ParallelSpringContextInstallStageTests.java @@ -17,6 +17,8 @@ package com.alipay.sofa.smoke.tests.isle; import com.alipay.sofa.boot.isle.stage.SpringContextInstallStage; +import org.junit.jupiter.api.condition.EnabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.springframework.test.context.TestPropertySource; /** @@ -26,6 +28,7 @@ * @version ParallelSpringContextInstallStageTests.java, v 0.1 2023年02月21日 8:06 PM huzijie Exp $ */ @TestPropertySource(properties = "sofa.boot.isle.moduleStartUpParallel=true") +@EnabledOnJre(JRE.JAVA_17) public class ParallelSpringContextInstallStageTests extends SpringContextInstallStageIntegrationTestBase { } diff --git a/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-isle/src/test/java/com/alipay/sofa/smoke/tests/isle/ParallelVirtualThreadSpringContextInstallStageTests.java b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-isle/src/test/java/com/alipay/sofa/smoke/tests/isle/ParallelVirtualThreadSpringContextInstallStageTests.java new file mode 100644 index 000000000..88e116de0 --- /dev/null +++ b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-isle/src/test/java/com/alipay/sofa/smoke/tests/isle/ParallelVirtualThreadSpringContextInstallStageTests.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alipay.sofa.smoke.tests.isle; + +import com.alipay.sofa.boot.isle.stage.SpringContextInstallStage; +import org.junit.jupiter.api.condition.EnabledOnJre; +import org.junit.jupiter.api.condition.JRE; +import org.springframework.test.context.TestPropertySource; + +/** + * Integration tests for {@link SpringContextInstallStage} in parallel when use virtual threads. + * + * @author huzijie + * @version ParallelVirtualThreadSpringContextInstallStageTests.java, v 0.1 2023年11月24日 4:03 PM huzijie Exp $ + */ +@TestPropertySource(properties = { "sofa.boot.startup.threads.virtual.enabled=true" }) +@EnabledOnJre(JRE.JAVA_21) +public class ParallelVirtualThreadSpringContextInstallStageTests extends + ParallelSpringContextInstallStageTests { +} diff --git a/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-runtime/src/test/java/com/alipay/sofa/smoke/tests/runtime/async/AsyncInitTests.java b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-runtime/src/test/java/com/alipay/sofa/smoke/tests/runtime/async/AsyncInitTests.java index 1f2834023..13b51ac55 100644 --- a/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-runtime/src/test/java/com/alipay/sofa/smoke/tests/runtime/async/AsyncInitTests.java +++ b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-runtime/src/test/java/com/alipay/sofa/smoke/tests/runtime/async/AsyncInitTests.java @@ -20,6 +20,8 @@ import com.alipay.sofa.runtime.async.AsyncInitMethodManager; import com.alipay.sofa.smoke.tests.runtime.RuntimeSofaBootApplication; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.TestConfiguration; @@ -41,6 +43,7 @@ @Import(AsyncInitTests.AsyncInitTestConfiguration.class) @TestPropertySource(properties = { "sofa.boot.runtime.asyncInitExecutorCoreSize=20", "sofa.boot.runtime.asyncInitExecutorMaxSize=20" }) +@EnabledOnJre(JRE.JAVA_17) public class AsyncInitTests { @Autowired diff --git a/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-runtime/src/test/java/com/alipay/sofa/smoke/tests/runtime/async/AsyncInitVirtualThreadTests.java b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-runtime/src/test/java/com/alipay/sofa/smoke/tests/runtime/async/AsyncInitVirtualThreadTests.java new file mode 100644 index 000000000..74c4c3047 --- /dev/null +++ b/sofa-boot-tests/sofa-boot-smoke-tests/sofa-boot-smoke-tests-runtime/src/test/java/com/alipay/sofa/smoke/tests/runtime/async/AsyncInitVirtualThreadTests.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alipay.sofa.smoke.tests.runtime.async; + +import com.alipay.sofa.runtime.async.AsyncInitMethodManager; +import org.junit.jupiter.api.condition.EnabledOnJre; +import org.junit.jupiter.api.condition.JRE; +import org.springframework.test.context.TestPropertySource; + +/** + * Integration tests for {@link AsyncInitMethodManager} when use virtual threads. + * + * @author huzijie + * @version AsyncInitVirtualThreadTests.java, v 0.1 2023年11月24日 4:00 PM huzijie Exp $ + */ +@TestPropertySource(properties = { "sofa.boot.startup.threads.virtual.enabled=true" }) +@EnabledOnJre(JRE.JAVA_21) +public class AsyncInitVirtualThreadTests extends AsyncInitTests { +}