functionaltests.async.AsyncPromiseSpec fails intermittently, and not always on the same feature, when the HTTP client gives up waiting for a response:
java.net.http.HttpTimeoutException: request timed out
at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:953)
at org.apache.grails.testing.http.client.HttpClientSupport$Trait$Helper.send(HttpClientSupport.groovy:1133)
at org.apache.grails.testing.http.client.HttpClientSupport$Trait$Helper.http(HttpClientSupport.groovy:203)
at functionaltests.async.AsyncPromiseSpec.<feature>(AsyncPromiseSpec.groovy:<line>)
The client timeout is 120 seconds (grails.http.client.timeout, set in gradle/functional-test-config.gradle).
Occurrences
Found by scanning the failed job logs of every CI and CI - Groovy Joint Validation Build run between 2026-07-20 and 2026-08-24 - 308 logs, none unavailable - for that spec together with that exception. These four are all of them in that period.
Three different features, two different applications (grails-test-examples-app1 and grails-test-examples-hibernate7-app1), both indy settings, and one of them on 8.0.x itself. Everything else in the same specification passes in the same run: on 2026-08-24, 695 tests completed, 1 failed.
Why a timeout here is odd
The 2026-08-24 failure was on /asyncTest/useAsyncService, and that action cannot take 120 seconds:
def useAsyncService() {
def input = params.input ?: 'hello'
def future = asyncProcessingService.processAsync(input)
def result = future.get(5, TimeUnit.SECONDS) // bounded
render([input: input, result: result] as JSON)
}
The worst it can do is throw after five seconds and return a 500. A client that waits 120 seconds and receives nothing suggests the request was never served, rather than that this action was slow.
Possibly relevant, not established
The feature declared immediately before it in the same controller deliberately abandons a thread on every data-driven case:
def workThread = Thread.start {
sleep(delay)
completed.set(true)
}
workThread.join(timeout) // 10 ms, while the work sleeps 100 ms
if (!completed.get()) {
workThread.interrupt()
message = "Task exceeded timeout of ${timeout} ms"
}
A raw thread per request, interrupted mid-sleep, is the kind of thing that leaves debris under load - but nothing here shows that it is what starves the later request, and the 2026-07-29 failures were on other features.
What has been ruled out
The promise decorator change in #16192 (AsyncWebRequestPromiseDecoratorLookupStrategy, which stops decorating a request the container will not start a new asynchronous cycle on). Two of the four occurrences are from 2026-07-29, three and a half weeks before that change was written, and one of those is on 8.0.x.
Reproducing
Not reproduced locally. :grails-test-examples-app1:integrationTest --tests "*AsyncPromiseSpec*" was run four times in a row - 84 tests, no failures - and the hibernate7 application's copy three times before that.
Suggested next step
The failure says only that the client gave up. What would settle it is the server's side of the same moment: whether the request arrived at all. An access log on these applications during functional tests, or a thread dump taken when a request exceeds the client timeout, would distinguish "never served" from "served slowly" - which is the fork this depends on.
Other CI flakes tracked separately
functionaltests.async.AsyncPromiseSpecfails intermittently, and not always on the same feature, when the HTTP client gives up waiting for a response:The client timeout is 120 seconds (
grails.http.client.timeout, set ingradle/functional-test-config.gradle).Occurrences
feat/8.0.x-legacy-command-compat8.0.xfix/embedded-mongo-replica-setci/…on #16214Found by scanning the failed job logs of every
CIandCI - Groovy Joint Validation Buildrun between 2026-07-20 and 2026-08-24 - 308 logs, none unavailable - for that spec together with that exception. These four are all of them in that period.Three different features, two different applications (
grails-test-examples-app1andgrails-test-examples-hibernate7-app1), both indy settings, and one of them on8.0.xitself. Everything else in the same specification passes in the same run: on 2026-08-24, 695 tests completed, 1 failed.Why a timeout here is odd
The 2026-08-24 failure was on
/asyncTest/useAsyncService, and that action cannot take 120 seconds:The worst it can do is throw after five seconds and return a 500. A client that waits 120 seconds and receives nothing suggests the request was never served, rather than that this action was slow.
Possibly relevant, not established
The feature declared immediately before it in the same controller deliberately abandons a thread on every data-driven case:
A raw thread per request, interrupted mid-sleep, is the kind of thing that leaves debris under load - but nothing here shows that it is what starves the later request, and the 2026-07-29 failures were on other features.
What has been ruled out
The promise decorator change in #16192 (
AsyncWebRequestPromiseDecoratorLookupStrategy, which stops decorating a request the container will not start a new asynchronous cycle on). Two of the four occurrences are from 2026-07-29, three and a half weeks before that change was written, and one of those is on8.0.x.Reproducing
Not reproduced locally.
:grails-test-examples-app1:integrationTest --tests "*AsyncPromiseSpec*"was run four times in a row - 84 tests, no failures - and the hibernate7 application's copy three times before that.Suggested next step
The failure says only that the client gave up. What would settle it is the server's side of the same moment: whether the request arrived at all. An access log on these applications during functional tests, or a thread dump taken when a request exceeds the client timeout, would distinguish "never served" from "served slowly" - which is the fork this depends on.
Other CI flakes tracked separately
develocity.apache.org … 403: Forbiddennoise. Whether that is a common cause or a common environment is not established.