CLI does not respond to Ctrl+C any longer
Ctrl+C is supposed to shutdown the CLI app and we also want to shutdown the lifecycle cleanly before exiting. To achieve this, we register a shutdown hook before starting the lifecycle which gets called in response to the signal.
Note: it looks like the shutdown hooks do not (and did never) run when the mailbox cli is run using gradle, i.e.
./gradlew run --args=-v won't trigger the shutdown hooks when a shutdown signal is received. So we need to build the jar using
./gradlew x86LinuxJar and run it using
java -jar mailbox-cli/build/libs/mailbox-cli-linux-x86_64.jar to observe if things are working or not.
Currently, pressing Ctrl+C leads to a deadlock. This is what is happening:
- Ctrl+C initiates a shutdown of the VM
- The shutdown hooks get started, including the one we registered before starting the lifecycle
- our shutdown hook calls
exitProcess(0)in its finally block
- This leads to deadlock.
The cause for the deadlock is described in the documentation of Runtime#exit:
Terminates the currently running Java virtual machine by initiating its shutdown sequence. [...]
The virtual machine's shutdown sequence consists of two phases. In the first phase all registered shutdown hooks, if any, are started in some unspecified order and allowed to run concurrently until they finish. In the second phase all uninvoked finalizers are run if finalization-on-exit has been enabled. Once this is done the virtual machine halts.
If this method is invoked after the virtual machine has begun its shutdown sequence then if shutdown hooks are being run this method will block indefinitely. If shutdown hooks have already been run and on-exit finalization has been enabled then this method halts the virtual machine with the given status code if the status is nonzero; otherwise, it blocks indefinitely.
The important part being:
If this method is invoked after the virtual machine has begun its shutdown sequence then if shutdown hooks are being run this method will block indefinitely
And that is precisely what we are doing. Actually we're calling
Runtime.exit() from within a shutdown hook which matches the conditions described above and causes indefinite blocking.
This got introduced with !99 (merged) and we discussed this here. I have the feeling it might not be the best idea after all to let the lifecycle manager exit the VM. Maybe it's better to let the individual starting points that use the lifecycle be responsible for that, i.e. the CLI, the Android app and the unit tests should handle that in some way that makes sense and works nicely in the respective scenario. I think a call to
Runtime.exit() within the lifecycle manager is just a too drastic measure which is difficult to make work in all scenarios and we're making our life a lot harder by trying to make the starting points work around the fact that the lifecycle manager is calling exit() itself.
If we use the mailbox in integration tests, this will be a problem too, because the test frameworks cannot gracefully handle components that exit the VM either. I recently added an option to disable this exiting behavior on the branch for integration tests with briar: a488bb20 and if I pass
exitAfterStopping in the CLI the exiting using CTRL+C also works as expected again. So maybe that is a middle ground where we can have the exiting in the lifecycle where we wanted it to be as a result of the discussion where it should be for the Android app, and yet still be able to disable this for both the CLI and integration tests.