Kill container from Gradle Run?

I notice that when using the Bndrun task with gradle, if I hit ctrl+c, or cancel from IntelliJ, the task exits immediately, but the container continues to run. I.e. the webconsole is still running, all of my services are still running.

I’d like to be able to quit the process from gradle or from the IDE.

I think this is somewhat related to #5249 on gradle github, and I believe the JavaExec task handles cancellation now via DefaultExecActionFactory

If I register a shutdown hook, I can see the logs if I kill the process via pkill, but not if I ctrl+C on the gradle task, which leads me to believe the way it is launched prevents the kill signal reaching the container?

Is it intended that one can kill the container process via cancelling the gradle task?

My testing project is currently at danelowe/osgi-runway on github

Are you running gradle --no-daemon? If you are using the gradle daemon, the bndrun process is probably a child of the daemon process which you don’t kill with Ctrl-C. (Just speculating.)

That would be nice but I am not sure what more should be done in the gradle task implementation.

Yeah it seems that --no-daemon allows the container to be killed, but doesn’t run the shutdown hook. I had thought that recent versions of gradle still used a ‘single-use’ daemon for --no-daemon, but it seems it has slightly different behaviour.

I do think there is probably something that can be done for use with a daemon, but might need some work in the ProjectLauncher, which I am yet to understand.

Gradle’s JavaExec task seems to use a cancellation token of some sort to deal with this.

I do know that Quarkus dev task works with gradle daemon and shuts down completely. I also note that the Bndrun task continually shows a progress meter while the container is running, which e.q. Quarkus does not show after the build is completed.

This proof-of-concept seems to work…

abstract class MyBndrun : AbstractBndrun<Project, Run>() {
    @Inject
    protected abstract fun getExecActionFactory(): org.gradle.process.internal.ExecActionFactory

    override fun worker(run: Project) {
        val launcher = run.projectLauncher
        launcher.prepare()
        val javaExecAction = getExecActionFactory().newJavaExecAction()
        javaExecAction.args = launcher.runProgramArgs.toList()
        javaExecAction.classpath = project.files(launcher.classpath)
        javaExecAction.mainClass.set(launcher.mainTypeName)
        javaExecAction.jvmArgs = launcher.runVM.toList()
        javaExecAction.args = launcher.runProgramArgs.toList()
//        javaExecAction.debug = true
        if (launcher.cwd != null) {
            javaExecAction.workingDir = launcher.cwd
        }
        javaExecAction.execute()
    }
}

Interesting. I am not keen to use internal gradle API though. Perhaps this could be done with an ExecOperations in a public API way?

Such a change would also need to activate the live coding support. And there are many other things that happen in ProjectLauncher.launch() that would also need to be covered.

Hmmm. I think the public API can be used like:

abstract class MyBndrun : AbstractBndrun<Project, Run>() {
    override fun worker(run: Project) {
        val launcher = run.projectLauncher
        launcher.prepare()
        val scheduledExecutor = Executors.newSingleThreadScheduledExecutor()
//        val args = if (run.`is`(aQute.bnd.osgi.Constants.JAVAAGENT))
//            launcher.agents.map { "-javaagent$it" }
//            else listOf()
        try {
            launcher.isTrace = run.isTrace || run.isRunTrace
            launcher.liveCoding(ForkJoinPool.commonPool(), scheduledExecutor)
            project.javaexec {
                classpath = project.files(launcher.classpath)
                mainClass.set(launcher.mainTypeName)
                jvmArgs = launcher.runVM.toList()
                args = launcher.runProgramArgs.toList()
//            debug = true
                if (launcher.cwd != null) {
                    workingDir = launcher.cwd
                }
            }
        } finally {
            scheduledExecutor.shutdownNow();
            aQute.bnd.gradle.BndUtils.logReport(run, logger);
        }
    }
}

At this point I’m yet to look into live coding (limited time allocated per day). Is it integrated with the build task somehow, or would ./gradlew build --continuous be run separately?

It watches the project’s output (e.g. bundles) and will update them in the running framework when they change. So you could be building them in an IDE (Bndtools in Eclipse) or from the command line.