diff --git a/server/src/main/java/com/defold/extender/services/HealthReporterService.java b/server/src/main/java/com/defold/extender/services/HealthReporterService.java index e4ec642e..314e78c9 100644 --- a/server/src/main/java/com/defold/extender/services/HealthReporterService.java +++ b/server/src/main/java/com/defold/extender/services/HealthReporterService.java @@ -8,10 +8,13 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.util.EntityUtils; @@ -61,6 +64,20 @@ public String collectHealthReport(boolean isRemoteBuildEnabled, Map> reportResults = new HashMap<>(remoteBuilderPlatformMappings.size()); List runningRequests = new ArrayList<>(); + // Probe each builder on its own thread from a dedicated pool: a slow or + // unresponsive builder must not starve the probes of the healthy ones, + // which is what happens on the shared common ForkJoinPool under load. + // Size to the exact builder count so no probe ever waits in the queue -- + // completeOnTimeout starts ticking at submission, so a queued probe could + // time out to false before its request ever runs. + ExecutorService healthCheckPool = Executors.newFixedThreadPool( + Math.max(1, remoteBuilderPlatformMappings.size()), + runnable -> { + Thread thread = new Thread(runnable, "health-check"); + thread.setDaemon(true); + return thread; + }); + try { for (Map.Entry entry : remoteBuilderPlatformMappings.entrySet()) { String instanceId = entry.getValue().getInstanceId(); String platform = getPlatform(entry.getKey()); @@ -80,6 +97,13 @@ public String collectHealthReport(boolean isRemoteBuildEnabled, Map innerRequest = CompletableFuture.supplyAsync(() -> { JSONParser parser = new JSONParser(); @@ -100,7 +124,7 @@ public String collectHealthReport(boolean isRemoteBuildEnabled, Map> status : reportResults.entrySet()) { @@ -128,6 +152,9 @@ public String collectHealthReport(boolean isRemoteBuildEnabled, Map conf = Map.of( + "linux-latest", new RemoteInstanceConfig("http://localhost:9678", "linux-latest", true), + "windows-latest", new RemoteInstanceConfig("http://localhost:9681", "windows-latest", true) + ); + JSONObject expected = new JSONObject(Map.of( + "linux", "Operational", + "windows", "Unreachable" + )); + JSONObject result = (JSONObject) new JSONParser().parse(service.collectHealthReport(true, conf)); + assertEquals(expected, result); + } }