Skip to content

Commit b084c51

Browse files
authored
Health check now non-blocking using fault tolerance async and timeout (helidon-io#2237)
Signed-off-by: Tomas Langer <tomas.langer@oracle.com>
1 parent ea36194 commit b084c51

4 files changed

Lines changed: 58 additions & 7 deletions

File tree

fault-tolerance/src/main/java/module-info.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@
2121
requires io.helidon.config;
2222
requires io.helidon.common.configurable;
2323
requires java.logging;
24+
25+
exports io.helidon.faulttolerance;
2426
}

health/health/pom.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!--
3-
Copyright (c) 2018, 2020 Oracle and/or its affiliates. All rights reserved.
3+
Copyright (c) 2018, 2020 Oracle and/or its affiliates.
44
55
Licensed under the Apache License, Version 2.0 (the "License");
66
you may not use this file except in compliance with the License.
@@ -37,6 +37,10 @@
3737
<groupId>io.helidon.media</groupId>
3838
<artifactId>helidon-media-jsonp</artifactId>
3939
</dependency>
40+
<dependency>
41+
<groupId>io.helidon.fault-tolerance</groupId>
42+
<artifactId>helidon-fault-tolerance</artifactId>
43+
</dependency>
4044
<dependency>
4145
<groupId>org.eclipse.microprofile.health</groupId>
4246
<artifactId>microprofile-health-api</artifactId>

health/health/src/main/java/io/helidon/health/HealthSupport.java

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package io.helidon.health;
1717

18+
import java.time.Duration;
1819
import java.util.Arrays;
1920
import java.util.Collection;
2021
import java.util.Collections;
@@ -26,6 +27,7 @@
2627
import java.util.Objects;
2728
import java.util.Optional;
2829
import java.util.Set;
30+
import java.util.concurrent.TimeUnit;
2931
import java.util.logging.Level;
3032
import java.util.logging.Logger;
3133
import java.util.stream.Collectors;
@@ -38,7 +40,10 @@
3840
import javax.json.JsonStructure;
3941

4042
import io.helidon.common.http.Http;
43+
import io.helidon.common.reactive.Single;
4144
import io.helidon.config.Config;
45+
import io.helidon.faulttolerance.Async;
46+
import io.helidon.faulttolerance.Timeout;
4247
import io.helidon.media.common.MessageBodyWriter;
4348
import io.helidon.media.jsonp.JsonpSupport;
4449
import io.helidon.webserver.Routing;
@@ -80,6 +85,8 @@ public final class HealthSupport implements Service {
8085
private final boolean backwardCompatible;
8186
private final CorsEnabledServiceHelper corsEnabledServiceHelper;
8287
private final MessageBodyWriter<JsonStructure> jsonpWriter = JsonpSupport.writer();
88+
private final Timeout timeout;
89+
private final Async async;
8390

8491
private HealthSupport(Builder builder) {
8592
this.enabled = builder.enabled;
@@ -112,6 +119,10 @@ private HealthSupport(Builder builder) {
112119
this.includedHealthChecks = Collections.emptySet();
113120
this.excludedHealthChecks = Collections.emptySet();
114121
}
122+
123+
124+
this.timeout = Timeout.create(Duration.ofMillis(builder.timeoutMillis));
125+
this.async = Async.create();
115126
}
116127

117128
@Override
@@ -127,20 +138,33 @@ public void update(Routing.Rules rules) {
127138
}
128139

129140
private void callAll(ServerRequest req, ServerResponse res) {
130-
send(res, callHealthChecks(allChecks));
141+
invoke(res, allChecks);
131142
}
132143

133144
private void callLiveness(ServerRequest req, ServerResponse res) {
134-
send(res, callHealthChecks(livenessChecks));
145+
invoke(res, livenessChecks);
135146
}
136147

137148
private void callReadiness(ServerRequest req, ServerResponse res) {
138-
send(res, callHealthChecks(readinessChecks));
149+
invoke(res, readinessChecks);
139150
}
140151

141-
private void send(ServerResponse res, HealthResponse hres) {
142-
res.status(hres.status());
143-
res.send(jsonpWriter.marshall(hres.json));
152+
void invoke(ServerResponse res, List<HealthCheck> healthChecks) {
153+
// timeout on the asynchronous execution
154+
Single<HealthResponse> result = timeout.invoke(() -> async.invoke(() -> callHealthChecks(healthChecks)));
155+
156+
// handle timeouts and failures in execution
157+
result = result.onErrorResume(throwable -> {
158+
LOGGER.log(Level.SEVERE, "Failed to call health checks", throwable);
159+
HcResponse response = new HcResponse(HealthCheckResponse.down("InternalError"), true);
160+
return new HealthResponse(Http.Status.INTERNAL_SERVER_ERROR_500, toJson(State.DOWN, List.of(response)));
161+
});
162+
163+
result.thenAccept(hres -> {
164+
res.status(hres.status());
165+
res.send(jsonpWriter.marshall(hres.json));
166+
});
167+
144168
}
145169

146170
HealthResponse callHealthChecks(List<HealthCheck> healthChecks) {
@@ -251,6 +275,8 @@ public static HealthSupport create(Config config) {
251275
* Fluent API builder for {@link io.helidon.health.HealthSupport}.
252276
*/
253277
public static final class Builder implements io.helidon.common.Builder<HealthSupport> {
278+
// 10 seconds
279+
private static final long DEFAULT_TIMEOUT_MILLIS = 10 * 1000;
254280
private final List<HealthCheck> allChecks = new LinkedList<>();
255281
private final List<HealthCheck> livenessChecks = new LinkedList<>();
256282
private final List<HealthCheck> readinessChecks = new LinkedList<>();
@@ -262,6 +288,7 @@ public static final class Builder implements io.helidon.common.Builder<HealthSup
262288
private boolean enabled = true;
263289
private boolean backwardCompatible = true;
264290
private CrossOriginConfig crossOriginConfig;
291+
private long timeoutMillis = DEFAULT_TIMEOUT_MILLIS;
265292

266293
private Builder() {
267294
}
@@ -371,12 +398,29 @@ public Builder config(Config config) {
371398
config.get("exclude").asList(String.class).ifPresent(list -> list.forEach(this::addExcluded));
372399
config.get("exclude-classes").asList(Class.class).ifPresent(list -> list.forEach(this::addExcludedClass));
373400
config.get("backward-compatible").asBoolean().ifPresent(this::backwardCompatible);
401+
config.get("timeout-millis").asLong().ifPresent(this::timeoutMillis);
374402
config.get(CORS_CONFIG_KEY)
375403
.as(CrossOriginConfig::create)
376404
.ifPresent(this::crossOriginConfig);
377405
return this;
378406
}
379407

408+
private void timeoutMillis(long aLong) {
409+
this.timeoutMillis = aLong;
410+
}
411+
412+
/**
413+
* Configure overall timeout of health check call.
414+
*
415+
* @param timeout timeout value
416+
* @param unit timeout time unit
417+
* @return updated builder instance
418+
*/
419+
public Builder timeout(long timeout, TimeUnit unit) {
420+
timeoutMillis(unit.toMillis(timeout));
421+
return this;
422+
}
423+
380424
/**
381425
* A class may be excluded from invoking health checks on it.
382426
* This allows configurable approach to disabling broken health-checks.

health/health/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
requires io.helidon.webserver.cors;
2727
requires io.helidon.media.jsonp;
2828
requires java.json;
29+
requires io.helidon.faulttolerance;
2930

3031
exports io.helidon.health;
3132
provides org.eclipse.microprofile.health.spi.HealthCheckResponseProvider with io.helidon.health.HealthCheckResponseProviderImpl;

0 commit comments

Comments
 (0)