This is an automated email from the ASF dual-hosted git repository.

shuber pushed a commit to branch unomi-3-dev
in repository https://gitbox.apache.org/repos/asf/unomi.git

commit 6f8578f7b35e1ea86e0c24c72fcb7039f076de76
Author: Jérôme Blanchard <[email protected]>
AuthorDate: Tue Dec 23 17:48:47 2025 +0100

    feat: ensure that healthchecks calls can be performed concurrently and add 
a 1s cache for performance or DoS issues.
---
 .../unomi/healthcheck/HealthCheckService.java      | 86 ++++++++++++++--------
 1 file changed, 56 insertions(+), 30 deletions(-)

diff --git 
a/extensions/healthcheck/src/main/java/org/apache/unomi/healthcheck/HealthCheckService.java
 
b/extensions/healthcheck/src/main/java/org/apache/unomi/healthcheck/HealthCheckService.java
index 9da585e14..f7da22e85 100644
--- 
a/extensions/healthcheck/src/main/java/org/apache/unomi/healthcheck/HealthCheckService.java
+++ 
b/extensions/healthcheck/src/main/java/org/apache/unomi/healthcheck/HealthCheckService.java
@@ -28,7 +28,6 @@ import org.slf4j.LoggerFactory;
 import javax.servlet.ServletException;
 import java.util.*;
 import java.util.concurrent.*;
-import java.util.stream.Collectors;
 
 import static org.apache.unomi.healthcheck.HealthCheckConfig.CONFIG_AUTH_REALM;
 
@@ -42,8 +41,12 @@ public class HealthCheckService {
     private static final Logger LOGGER = 
LoggerFactory.getLogger(HealthCheckService.class.getName());
 
     private final List<HealthCheckProvider> providers = new ArrayList<>();
+    private final Object cacheLock = new Object();
+    private volatile long cacheTimestamp = 0L;
+    private volatile List<HealthCheckResponse> healthCache = 
Collections.emptyList();
+    private volatile boolean initialized = false;
+    private volatile boolean busy = false;
     private ExecutorService executor;
-    private boolean busy = false;
     private boolean registered = false;
 
     @Reference
@@ -58,7 +61,7 @@ public class HealthCheckService {
     @Activate
     public void activate() throws ServletException, NamespaceException {
         LOGGER.info("Activating healthcheck service...");
-        executor = Executors.newSingleThreadExecutor();
+        executor = Executors.newCachedThreadPool();
         if (!registered) {
             setConfig(config);
         }
@@ -114,37 +117,60 @@ public class HealthCheckService {
         providers.remove(provider);
     }
 
-    public List<HealthCheckResponse> check() throws RejectedExecutionException 
{
-        if (config !=null && config.isEnabled()) {
-            LOGGER.debug("Health check called");
-            if (busy) {
-                throw new RejectedExecutionException("Health check already in 
progress");
-            } else {
-                try {
-                    busy = true;
-                    List<HealthCheckResponse> health = new ArrayList<>();
-                    health.add(HealthCheckResponse.live("karaf"));
-                    for (HealthCheckProvider provider : 
providers.stream().filter(p -> 
config.getEnabledProviders().contains(p.name())).collect(Collectors.toList())) {
-                        Future<HealthCheckResponse> future = 
executor.submit(provider::execute);
-                        try {
-                            HealthCheckResponse response = 
future.get(config.getTimeout(), TimeUnit.MILLISECONDS);
-                            health.add(response);
-                        } catch (TimeoutException e) {
-                            future.cancel(true);
-                            health.add(provider.timeout());
-                        } catch (Exception e) {
-                            LOGGER.error("Error while executing health check", 
e);
-                        }
+    public List<HealthCheckResponse> check() {
+        if (config == null || !config.isEnabled()) {
+            LOGGER.info("Healthcheck service is disabled");
+            return Collections.emptyList();
+        }
+        if (!initialized) {
+            synchronized (cacheLock) {
+                if (!initialized) {
+                    refreshCacheSync();
+                    initialized = true;
+                }
+            }
+        } else if (shouldRefreshCache()) {
+            if (!busy) {
+                synchronized (cacheLock) {
+                    if (!busy) {
+                        busy = true;
+                        executor.submit(() -> {
+                            try {
+                                refreshCacheSync();
+                            } finally {
+                                busy = false;
+                            }
+                        });
                     }
-                    return health;
-                } finally {
-                    busy = false;
                 }
             }
-        } else {
-            LOGGER.info("Healthcheck service is disabled");
-            return Collections.emptyList();
         }
+        return healthCache;
+    }
+
+    private boolean shouldRefreshCache() {
+        return healthCache.isEmpty() || (System.currentTimeMillis() - 
cacheTimestamp) > 1000;
     }
 
+    private void refreshCacheSync() {
+        try {
+            List<HealthCheckResponse> health = new ArrayList<>();
+            health.add(HealthCheckResponse.live("karaf"));
+            for (HealthCheckProvider provider : providers.stream()
+                    .filter(p -> 
config.getEnabledProviders().contains(p.name()))
+                    .toList()) {
+                try {
+                    HealthCheckResponse response = provider.execute();
+                    health.add(response);
+                } catch (Exception e) {
+                    LOGGER.error("Error while executing health check", e);
+                    health.add(provider.timeout());
+                }
+            }
+            healthCache = health;
+            cacheTimestamp = System.currentTimeMillis();
+        } catch (Exception e) {
+            LOGGER.error("Error refreshing health cache", e);
+        }
+    }
 }

Reply via email to