diff --git a/reactive/fault-tolerance/src/main/java/io/helidon/reactive/faulttolerance/FaultTolerance.java b/reactive/fault-tolerance/src/main/java/io/helidon/reactive/faulttolerance/FaultTolerance.java index a5f90a2b836..837813d5d2a 100644 --- a/reactive/fault-tolerance/src/main/java/io/helidon/reactive/faulttolerance/FaultTolerance.java +++ b/reactive/fault-tolerance/src/main/java/io/helidon/reactive/faulttolerance/FaultTolerance.java @@ -25,6 +25,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Flow; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; @@ -58,6 +59,7 @@ public final class FaultTolerance { new AtomicReference<>(); private static final AtomicReference> EXECUTOR = new AtomicReference<>(); private static final AtomicReference CONFIG = new AtomicReference<>(Config.empty()); + private static final AtomicBoolean INITIALIZED = new AtomicBoolean(false); static { SCHEDULED_EXECUTOR.set(LazyValue.create(ScheduledThreadPoolSupplier.builder() @@ -106,13 +108,25 @@ public static void scheduledExecutor(Supplier executor() { + INITIALIZED.set(true); return EXECUTOR.get(); } static LazyValue scheduledExecutor() { + INITIALIZED.set(true); return SCHEDULED_EXECUTOR.get(); } + /** + * Mostly used for testing. This class is considered to be initialized if any of its + * (lazy valued) executors were returned. + * + * @return boolean indicating whether init took place + */ + public static boolean initialized() { + return INITIALIZED.get(); + } + /** * A builder to configure a customized sequence of fault tolerance handlers. * diff --git a/reactive/health/pom.xml b/reactive/health/pom.xml index 4ee0b409ae3..ec1d03a1819 100644 --- a/reactive/health/pom.xml +++ b/reactive/health/pom.xml @@ -99,4 +99,39 @@ test + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + default-test + + test + + test + + + **/HealthSupportInitTest.java + + + + + health-support-init-test + + test + + test + + + **/HealthSupportInitTest.java + + + + + + + diff --git a/reactive/health/src/main/java/io/helidon/reactive/health/HealthSupport.java b/reactive/health/src/main/java/io/helidon/reactive/health/HealthSupport.java index e04e4211edd..797fa80760b 100644 --- a/reactive/health/src/main/java/io/helidon/reactive/health/HealthSupport.java +++ b/reactive/health/src/main/java/io/helidon/reactive/health/HealthSupport.java @@ -33,6 +33,7 @@ import java.util.logging.Logger; import java.util.stream.Collectors; +import io.helidon.common.LazyValue; import io.helidon.common.http.Http; import io.helidon.common.reactive.Single; import io.helidon.config.Config; @@ -87,8 +88,8 @@ public final class HealthSupport extends HelidonRestServiceSupport { private final Set includedHealthChecks; private final Set excludedHealthChecks; private final MessageBodyWriter jsonpWriter = JsonpSupport.writer(); - private final Timeout timeout; - private final Async async; + private final LazyValue timeout; + private final LazyValue async; private HealthSupport(Builder builder) { super(LOGGER, builder, SERVICE_NAME); @@ -111,9 +112,9 @@ private HealthSupport(Builder builder) { this.excludedHealthChecks = Collections.emptySet(); } - - this.timeout = Timeout.create(Duration.ofMillis(builder.timeoutMillis)); - this.async = Async.create(); + // Lazy values to prevent early init of maybe-not-yet-configured FT thread pools + this.timeout = LazyValue.create(() -> Timeout.create(Duration.ofMillis(builder.timeoutMillis))); + this.async = LazyValue.create(Async::create); } @Override @@ -186,7 +187,8 @@ private void head(ServerResponse res, Collection void invoke(ServerResponse res, Collection healthChecks, boolean sendDetails) { // timeout on the asynchronous execution - Single result = timeout.invoke(() -> async.invoke(() -> callHealthChecks(healthChecks))); + Single result = timeout.get().invoke( + () -> async.get().invoke(() -> callHealthChecks(healthChecks))); // handle timeouts and failures in execution result = result.onErrorResume(throwable -> { diff --git a/reactive/health/src/test/java/io/helidon/reactive/health/HealthSupportInitTest.java b/reactive/health/src/test/java/io/helidon/reactive/health/HealthSupportInitTest.java new file mode 100644 index 00000000000..11ec8505dd4 --- /dev/null +++ b/reactive/health/src/test/java/io/helidon/reactive/health/HealthSupportInitTest.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed 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 io.helidon.reactive.health; + +import io.helidon.reactive.faulttolerance.FaultTolerance; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; + +public class HealthSupportInitTest { + + /** + * Must be executed at startup, preferably in its own VM. Verifies that fault tolerance + * is not initialized too early due to execution of health extension. + */ + @Test + void checkLazyFaultToleranceInitialization() { + HealthSupport support = HealthSupport.create(); + assertThat(support, notNullValue()); + assertThat(FaultTolerance.initialized(), is(false)); + } +}