jamesfredley opened a new pull request, #15698: URL: https://github.com/apache/grails-core/pull/15698
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]
