codeconsole opened a new pull request, #16338:
URL: https://github.com/apache/grails-core/pull/16338

   ### Problem
   
   Neo4j and MongoDB functional jobs on `8.0.x` were cancelled at GitHub's 
six-hour limit six times between 2026-08-26 and 2026-09-11, most recently [this 
Neo4j 
job](https://github.com/apache/grails-core/actions/runs/34655597581/job/103452387382).
 Each hung in `:grails-data-mongodb-embedded:test`: three after every spec had 
passed, and three while `stopping a server that is already stopped is what a 
shutdown hook does` was running.
   
   An ordinary shutdown stops the embedded server twice. 
`EmbeddedMongoLifecycle` stops it when the application context closes, and the 
shutdown hook `EmbeddedMongoInitializer` registers stops it again as the JVM 
exits. The in-memory backend called `MongoServer.shutdownNow()` both times:
   
   ```java
   @Override
   public void stop() {
       this.server.shutdownNow();
       this.running = false;
   }
   ```
   
   A second `shutdownNow()` can wait forever. A client that connected just as 
the first shutdown began can be registered by Netty after its event loop has 
closed its channels, so the loop terminates with the client still in 
mongo-java-server's channel group, and `closeClients()` waits, with no timeout, 
on a close the terminated loop never completes. Gradle's test worker exits 
through `System.exit`, which waits for every shutdown hook, so the task never 
finished.
   
   The Neo4j jobs ran these tests at all because `mongodb-test-config.gradle` 
did not skip its suites under `-PonlyNeo4jTests`.
   
   ### Fix
   
   `RunningInMemoryMongo.stop()` returns when the server is already stopped, 
and `stop()` and `restart()` are synchronized, since the context's shutdown 
hook and the initializer's run at the same time on the way out:
   
   ```java
   @Override
   public synchronized void stop() {
       if (!this.running) {
           return;
       }
       this.server.shutdownNow();
       this.running = false;
   }
   ```
   
   The rest is the test and CI configuration this exposed:
   
   - `EmbeddedMongoReplicaSetSpec` stops its servers through the lifecycle 
bean. It called `close()` on contexts that were never refreshed, which does 
nothing, so its four mongods were left for the shutdown hook.
   - `mongodb-test-config.gradle`, `hibernate5-test-config.gradle`, 
`hibernate7-test-config.gradle` and `redis-test-config.gradle` skip their 
suites under `-PonlyNeo4jTests`. The Neo4j jobs had been running the MongoDB 
suites and 19 Hibernate test tasks that the MongoDB and Hibernate jobs already 
run.
   - `grails-redis` applies only `redis-test-config.gradle`, as the Hibernate 
and MongoDB modules apply only their own config. With `test-config.gradle` 
applied as well, its `test` and `integrationTest` tasks ran in no CI job, the 
Redis jobs included.
   - The nine test jobs set `timeout-minutes` to twice the slowest run that 
finished on its own, passed or failed, over the 160 CI runs from 2026-09-03 to 
2026-09-13, rounded up to the half hour: 90 minutes for the Spring Security 
config and CAS jobs, 120 to 210 for the rest. The build jobs keep GitHub's 
default, since the macOS build has passed after more than five hours.
   
   The unbounded wait itself is fixed in mongo-java-server by 
[bwaldvogel/mongo-java-server#256](https://github.com/bwaldvogel/mongo-java-server/pull/256).
   
   **Worth calling out:**
   - `grails-redis` no longer takes the heap, fork, `--add-opens` and CI 
compile-cache settings from `test-config.gradle`, the same as the Hibernate and 
MongoDB modules. Its tests pass without them against Redis 7.4 and 8.0.
   - The timeouts come from ten days of runs, and a job whose suite grows past 
half of its limit will need a higher one.
   
   ### Tests
   
   `an application whose context stopped its in-memory server still exits` runs 
an application in a JVM of its own that lets a client connect, stops the server 
as its context closes, and returns from `main`. Without the fix that JVM never 
exited in 8 of 8 runs, and the feature fails after 30 seconds instead of 
hanging; with the fix the JVM exits in about a second. The application, 
`ContextStoppedServerApplication`, is written in Java: started through Groovy 
and Spock, the same steps almost never hang even unfixed.
   
   The existing feature that stops a server twice now waits a bounded time for 
the second stop, so a regression fails instead of hanging the build.
   
   `:grails-data-mongodb-embedded:test`, and `:grails-redis:test` with 
`:grails-redis:integrationTest`, are green.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to