Hi @borkdude - I'm playing with the cool TUI stuff you recently integrated, and I noticed that if I ^C while doing charm stuff, it typically has hidden my cursor. So this led me to try to add a shutdown hook, which the docs say should run even in the event of a ^C interrupt - but when I use charm, my shutdown hook doesn't get called. Here is a small reproduction case:
#!/usr/bin/env bb
(require '[babashka.deps])
(babashka.deps/add-deps
'{:deps {io.github.TimoKramer/charm.clj {:git/sha "a6657a8567758907d0b2d24cc1529de2b39c2640"}}})
(require '[charm.core :as charm])
(-> (Runtime/getRuntime) (.addShutdownHook (Thread. #(println "bye"))))
(charm/run {:init {:count 0}
:update (fn [state _] [state nil])
:view (fn [state] (str "Count: " (:count state)))})
Try to reproduce it using Jline only
Yes, it looks like just this is sufficient to cause it to happen:
(import '[org.jline.terminal TerminalBuilder])
(-> (Runtime/getRuntime) (.addShutdownHook (Thread. #(println "bye"))))
(-> (TerminalBuilder/builder)
(.build))
(println "Interrupt in the next 5s ...")
(Thread/sleep 5000)
If you ^C, the hook doesn't run, but upon normal exit (ie, when the sleep expires) it does.Anyway, presumably not a babashka issue - just surprising to me that this is even possible.
This works for me.
$ bb /tmp/dude.clj
Interrupt in the next 5s ...
^C
$
Try it in a fresh terminal. The terminal may be funky because of some stuff you did to it earlierthis is your last example
Nope, still doesn't work for me - I tried in a new ghostty window, and also a new native terminal window.
That's interesting though - maybe a platform issue on the mac? Hmmm.
Thank you for trying it.
I'm on a mac as well. iTerm2
mac aarch64 (m4)
Lemme download iTerm2 really quick to try ...
Nope, still no hook. In any case, this means it must be on my side somehow (?) -- I'll let you know if I happen to figure it out.
ah wait, you mean, the shutdown hook doesn't fire? I thought the problem was: ctrl-c doesn't exit the program.
No, the issue is that the shutdown hook doesn't fire.
ah yes, I can reproduce that
does it also happen on JVM Clojure?
Basically, charm messes up my terminal - it hides my cursor - so I was just trying to manually do something to emit a sequence to show my cursor again when the program exits ...
Haven't tried on JVM yet.
JVM clojure same problem:
$ clojure -Sdeps '{:deps {org.jline/jline-terminal {:mvn/version "3.30.6"}}}' -M /tmp/dude.clj
Interrupt in the next 5s ...
^CYou're faster than me! I was only halfway there ... heh.
I'm sort of shocked that that is possible. Dang.
I let Claude take a look at jline3's code:
Found it. Look at line 92-99:
When signalHandler == SignalHandler.SIG_DFL (the default, set in AbstractTerminal constructor at line 87), jline calls Signals.registerDefault(signal.name()) for all signals including INT.
Signals.registerDefault (line 112-121 of Signals.java) registers SIG_DFL — the default OS signal handler — which for SIGINT means immediately terminate the process without running shutdown hooks. That's Runtime.halt() behavior rather than System.exit().
So the problem is: jline replaces the JVM's default SIGINT handler (which calls System.exit and runs shutdown hooks) with the OS-level SIG_DFL (which just kills the process). User-registered shutdown hooks never get a chance to run.
The fix would be for jline to either:
1. Not register SIG_DFL for INT (leave the JVM's default handler in place)
2. Register a handler that calls System.exit() instead of using SIG_DFLVery interesting ... and surprising to me that this is in jline.
This should work:
(import '[org.jline.terminal TerminalBuilder])
(-> (Runtime/getRuntime) (.addShutdownHook (Thread. #(println "bye"))))
(-> (TerminalBuilder/builder)
(.nativeSignals false)
(.build))
(println "Interrupt in the next 5s ...")
(Thread/sleep 5000)(.nativeSignals false)
Indeed it does work - but that's something that charm would have to do to be useful, unless I'm mistaken.
I really should start reaching for AI (as you did) when researching something like this.
I just don't have the reflex for it yet.
yep. discuss with @timok
no problem, this is useful to know for all bb users
It would be a good change for him to incorporate probably, because he has a finally block where he tries to restore terminal defaults when the program finishes, but it won't run if it gets interrupted like this ... which explains my cursor always being hidden by charm.
👍
Sorry for the delay in responding @timok - I'll take a look soon, but thanks for doing this and for letting me know.
I solved it now with looping the ctrl+c to the message channel so you as a consumer of the lib can interact as you want with the signal. hope this works for you
cool, thanks for investigating... @pmooser in case you want to contribute I am happy to take a PR
It suppose it must be a charm issue ...
On a side note, it's cool that babashka conf filled up, although I dragged my butt for too long - next year maybe !