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

   Fixes #13695
   
   ## Problem
   
   `stop-app` in the interactive Grails shell does not work. The base profile 
`stop-app` command tries two mechanisms, both of which fail on a modern setup:
   
   1. **JMX** via `com.sun.tools.attach.VirtualMachine`, loading 
`management-agent.jar` - that jar was removed in Java 9+, and the code uses 
`javax.*`. This silently fails on JDK 17/21.
   2. **HTTP fallback** that POSTs to the Spring Boot Actuator 
`/actuator/shutdown` endpoint - disabled by default, so it throws 
`FileNotFoundException` (HTTP 404), reporting "Application not running" even 
though it is.
   
   Enabling the Actuator shutdown endpoint is not an acceptable fix: it is 
intrusive and affects production for the sake of a development-time `stop-app`.
   
   ## Approach (CLI only, no Actuator, no JMX)
   
   In the interactive shell, `run-app` starts the application as an 
asynchronous Gradle `bootRun` build. That build already has a Gradle Tooling 
API `CancellationTokenSource` - it is what CTRL-C uses to stop the app. The 
problem is that the token was only reachable through the `run-app` command's 
`ExecutionContext`, so `stop-app` (a separate `ExecutionContext` in the same 
CLI JVM) had no handle to it.
   
   This PR introduces a small shared registry so `stop-app` can cancel the 
running build directly, without `POOL.shutdownNow()` (which would kill the 
executor and block re-running) and without exiting the CLI.
   
   ## Changes
   
   - **New `RunningApplicationRegistry`** (`grails-shell-cli`) - tracks the 
`CancellationTokenSource` of running `run-app` builds. `stopAll()` only 
requests cancellation; the build deregisters its own token when it finishes. 
`awaitStop(timeout)` waits for the build to tear down.
   - **`GradleUtil`** - `wireCancellationSupport` now returns the token source, 
created via the public `GradleConnector.newCancellationTokenSource()` instead 
of the internal `DefaultCancellationTokenSource`. A new 
`runBuildWithConsoleOutput(context, trackForStop, closure)` overload 
registers/deregisters the token around the build.
   - **`GradleInvoker`** - tracks only the `bootRun` task (matches `bootRun` or 
`:bootRun`), so transient builds (compile, test, console) are not affected.
   - **`stop-app.groovy`** - rewritten to cancel via the registry and wait for 
shutdown; obsolete `port`/`host` flags removed.
   - **`run-app.groovy`** - no longer passes 
`-Dgrails.management.endpoints.shutdown.enabled=true` (only existed to enable 
the Actuator endpoint); the JVM shutdown hook now calls 
`RunningApplicationRegistry.stopAll()` directly instead of re-invoking the 
`stop-app` command.
   - **Docs** - updated `stop-app` reference and the getting-started 
running/debugging guide.
   
   ## Tests
   
   - `RunningApplicationRegistrySpec` covers register/deregister, `stopAll` 
requesting cancellation without removing tokens, resilience when a token 
throws, and `awaitStop` success/timeout.
   - `./gradlew :grails-shell-cli:test --tests 
"org.grails.cli.gradle.RunningApplicationRegistrySpec"` passes.
   
   ## Manual verification still recommended
   
   The forked application JVM is torn down by Gradle when the `bootRun` build 
is cancelled (the same path CTRL-C uses). A manual run of `grails run-app` -> 
`grails stop-app` -> `grails run-app` on JDK 21 (Windows and Linux) is 
recommended before merge to confirm no orphaned process and that the port is 
freed.
   
   ## Notes
   
   - This is a draft for review of the approach on `7.0.x`. The linked issue is 
milestoned for 8.0.0-M3; opening here first per discussion.
   


-- 
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