Hi. I'd like to understand, is there a way to cust...
# community-support
m
Hi. I'd like to understand, is there a way to customize how the GradleDaemon stops a JavaExec process, in response to "the build being cancelled", such as, typing Ctrl-C in the terminal running
./gradlew runApp
(where
runApp
is a JavaExec). It appears to me the Java process is killed outright (possibly with SIGKILL). I would much prefer to have the daemon send either TERM or INT, wait a few seconds, then KILL. This would give the Java main a chance to do a graceful shutdown. My task is not a plain JavaExec due to config cache incompatibility, instead it's a regular Task with a doLast that includes ExecOperations.javaExec. I doubt that's relevant to the question though.
t
On Linux, a shutdown hook (
Runtime.getRuntime().addShutdownHook()
) is correctly called when Ctrl+C'ing a JavaExec task in my testing.
v
"correctly called" as far as they are "correctly called" anyway I guess. šŸ˜„ As shutdown hooks are never guaranteed to run. šŸ™‚
m
When executing via CLI, I normally see 3 processes as follows: (1) GradleWrapperMain, (2) GradleDaemon, (3) AppMain. My AppMain contains signal handlers for TERM and INT, but it's not getting any signals forwarded to it, which makes me think it's getting KILL directly, which we cannot catch. I tried using
addShutdownHook
in
build.gradle.kts
, but that adds the hook to the daemon process (2). When we click Ctrl-C in the terminal: • GradleWrapperMain (1) gets the INT and disconnects from the daemon • The daemon (2) prints a message like "client disconnection detected". It then (presumably) KILL-s the AppMain, but it doesn't shut down itself (unless using
--no-daemon
I guess) I don't see how shutdown hooks can help here.
t
I have a shutdown hook in my Java app, run by the JavaExec task. (actually, this is using Jetty, so Jetty is setting up its own shutdown hook for graceful shutdown) I ran
./gradlew run --debug-jvm
, attached a debugger to my app, set a breakpoint in the shutdown hook, and the breakpoint was hit when I Ctrl+C the gradle wrapper in the terminal.
A KILL wouldn't run the shutdown hook, so Gradle doesn't sent a KILL at least.
As it does not use
destroyForcibly()
which should do a sigkill but a
destroy()
, it should sent or at least try with a sigterm and thus give the process a chance to shutdown cleanly.
But actually it is depending on the used JVM how it implements that method and could also forcibly kill immediately.
m
Using Gradle 8.12 on RHEL 8.10, if I click Ctrl-C in the terminal, the output stops immediately. On the terminal I see a
^C
on a new line, then the next prompt. In the daemon log file, the next line below the app output is
thread 22: client disconnection detected, canceling the build
, followed by
FAILURE: Build failed with an Exception
. Actually, my App might get the INT but not have a chance for the output to make it into either the daemon log or the terminal. Ideally, I would like to: • Have the terminal display whatever my App is doing after receiving the INT (assuming it does, which I'm not sure) • Have the build not marked as FAILED if the app stops in reasonable time say <10s
v
You killed the build, it will not be successful. I guess not even if you configure to ignore exit codes. Regarding the output, I don't think you will get it. Afair, and the quote by you supports that, the CLI is killed by the signal, the daemon sees the CLI is gone for good and thus cancels the build.
m
So the question would be, are there any extension points to alter this behaviour? If you ask why would I want to run the App via Gradle in the first place, the answer has to do with providing that ability inside an IDE for local development loop. Currently I have a RunConfig of type Gradle which invokes a task like
runMyApp
. What bothers me is that if I click "stop" in the IDE (which is the functional equivalent of pressing Ctrl-C in the terminal), I expect my app to get the signal and have a chance to do an orderly shutdown. So yes, technically I manually interrupted the Gradle JavaExec task, but if the app runs a few more seconds and stops cleanly by itself, I would much rather NOT mark the task as FAILED.
v
Not that I know of
m
By comparison, if I run the app from the CLI with some kind of
run-java
, pressing Ctrl-C has desired/expected the effect of showing the app output as it shuts down, followed by terminal exit code 0 success.
So the abstraction which might not exist today is that for some tasks (notably involving Exec/JavaExec), it might be "expected" to run until manually stopped, in which case it might be better to offer a "softer" stop, such as INT/TERM+sleep+KILL. Also, the log/terminal should not cut off before the final kill. In contrast, for most build-related tasks, such as Compile, the ability to interrupt them doesn't really make sense. For your consideration...
e
you should have a start script for your application. then you can configure the IDE to "run script (build first)", and terminating it will do the right thing
m
Yes, I'm trying this now... I'm weary of moving too much logic (e.g. the task dependency, the classpath recomputation if there are included builds, etc) into IDE specifics, I'd much rather do everything in Gradle and be IDE agnostic.
e
Gradle already can create scripts (automatically with
plugin { application }
./gradlew install
build/install/*/bin/*
, or manually with https://docs.gradle.org/current/dsl/org.gradle.jvm.application.tasks.CreateStartScripts.html)
v
For your consideration...
We are just users like you. If you want to suggest such changes officially, you need to open a feature request to see what the Gradle folks think.
šŸ‘ 1
m
I tried other way of starting an app, but I see no way to gracefully shut down a run. I'm using IntelliJ. My options from starting an app from inside the IDE are to use a RunConfiguration of type Application or Gradle or Shell Script. • Gradle RunConfig: The issue described above. I can start with/without debugger, which is good. But when running without debugger, I cannot stop gracefully. If I click "Stop", I confirm Gradle sends a SIGTERM to my program, which is great and I am catching, but Gradle also marks the build as failed, and I don't see the output of the program as it is shutting down. • Application RunConfig: In a Gradle build, it looks to me as if IntelliJ is creating some custom Gradle scripts to execute a Main class, which results in a (generated) JavaExec task, which has the exact same problem as a Gradle RunConfig. • Shell Script RunConfig: This is not a serious option, because I cannot start with debugger attached and use breakpoints, because there's no standard way to pass the relevant agent jvm args in a free-form script. IntelliJ actually opens a tab in the Console window to show the output of a script RunConfig, as opposed to using a tab in the Run window. I think these are really meant to run scripts, not to start Java apps. I'd like to put in a feature request for this, is the process described somewhere?
v
• Application RunConfig: In a Gradle build, it looks to me as if IntelliJ is creating some custom Gradle scripts to execute a Main class, which results in a (generated) JavaExec task, which has the exact same problem as a Gradle RunConfig.
You can disable the default delegation to Gradle for running classes in the Gradle settings of the IDE:
I'd like to put in a feature request for this, is the process described somewhere?
Just open a new issue and select "Feature Request" as type, the template will tell you then what to fill in
šŸ‘ 1
m
I would think of "Run using IDE" as a regression šŸ™‚ I mean, this might work for some cases, but the moment we do non-trivial things in Gradle, I doubt the IDE would understand, as the message suggests...
v
Yes. Alternatively you can also manually create a run configuration that has as a "do before" step "./gradlew installDist" and then runs the distribution prepared by Gradle in
build/install/<appname>
that also works fine, I did that myself on some occasions too.
Or you just live with the "BUILD FAILED" when you abort the program.
m
Yep
v
Or you add some graceful exit from inside. As long as you do not kill from outside, but for example have an "exit" functionality in your application that either (bad big hammer method) calls
System.exit(0)
or better orderly ends all non-daemon threads, then the JVM can just shut down normally and the Gradle build will be successful.