diff --git a/health/health-checks/src/main/java/io/helidon/health/checks/DeadlockHealthCheck.java b/health/health-checks/src/main/java/io/helidon/health/checks/DeadlockHealthCheck.java index e8a3870b770..ae926996819 100644 --- a/health/health-checks/src/main/java/io/helidon/health/checks/DeadlockHealthCheck.java +++ b/health/health-checks/src/main/java/io/helidon/health/checks/DeadlockHealthCheck.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023 Oracle and/or its affiliates. + * Copyright (c) 2018, 2025 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. @@ -19,6 +19,7 @@ import java.lang.System.Logger.Level; import java.lang.management.ManagementFactory; import java.lang.management.ThreadMXBean; +import java.util.Arrays; import io.helidon.common.NativeImageHelper; import io.helidon.health.HealthCheck; @@ -95,17 +96,22 @@ public HealthCheckResponse call() { .build(); } - boolean noDeadLock = false; + HealthCheckResponse.Builder builder = HealthCheckResponse.builder(); + try { // Thanks to https://stackoverflow.com/questions/1102359/programmatic-deadlock-detection-in-java#1102410 - noDeadLock = (threadBean.findDeadlockedThreads() == null); + long[] deadlockedThreads = threadBean.findDeadlockedThreads(); + if (deadlockedThreads != null) { + builder.status(Status.DOWN); + LOGGER.log(Level.TRACE, "Health check observed deadlocked threads: " + Arrays.toString(deadlockedThreads)); + } } catch (Throwable e) { - // ThreadBean does not work - probably in native image - LOGGER.log(Level.TRACE, "Failed to find deadlocks in ThreadMXBean, ignoring this healthcheck", e); + // ThreadBean does not work - probably in native image. Report ERROR, not DOWN, because we do not know that + // there are deadlocks which DOWN should imply; we simply cannot find out. + LOGGER.log(Level.TRACE, "Error invoking ThreadMXBean to find deadlocks; cannot complete this healthcheck", e); + builder.status(Status.ERROR); } - return HealthCheckResponse.builder() - .status(noDeadLock ? Status.UP : Status.DOWN) - .build(); + return builder.build(); } } diff --git a/health/health-checks/src/test/java/io/helidon/health/checks/DeadlockHealthCheckTest.java b/health/health-checks/src/test/java/io/helidon/health/checks/DeadlockHealthCheckTest.java index 70f3079da42..9fa4e5d5f5a 100644 --- a/health/health-checks/src/test/java/io/helidon/health/checks/DeadlockHealthCheckTest.java +++ b/health/health-checks/src/test/java/io/helidon/health/checks/DeadlockHealthCheckTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022 Oracle and/or its affiliates. + * Copyright (c) 2018, 2025 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. @@ -63,4 +63,13 @@ void noDeadlockDetected() { HealthCheckResponse response = check.call(); assertThat(response.status(), is(HealthCheckResponse.Status.UP)); } + + @Test + + void errorInvokingMBean() { + Mockito.when(threadBean.findDeadlockedThreads()).thenThrow(new RuntimeException("Simulated error invoking MBean")); + DeadlockHealthCheck check = new DeadlockHealthCheck(threadBean); + HealthCheckResponse response = check.call(); + assertThat(response.status(), is(HealthCheckResponse.Status.ERROR)); + } }