1515 */
1616package io .helidon .health ;
1717
18+ import java .time .Duration ;
1819import java .util .Arrays ;
1920import java .util .Collection ;
2021import java .util .Collections ;
2627import java .util .Objects ;
2728import java .util .Optional ;
2829import java .util .Set ;
30+ import java .util .concurrent .TimeUnit ;
2931import java .util .logging .Level ;
3032import java .util .logging .Logger ;
3133import java .util .stream .Collectors ;
3840import javax .json .JsonStructure ;
3941
4042import io .helidon .common .http .Http ;
43+ import io .helidon .common .reactive .Single ;
4144import io .helidon .config .Config ;
45+ import io .helidon .faulttolerance .Async ;
46+ import io .helidon .faulttolerance .Timeout ;
4247import io .helidon .media .common .MessageBodyWriter ;
4348import io .helidon .media .jsonp .JsonpSupport ;
4449import 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.
0 commit comments