I'm trying to implement this behavior:
When user types Ctrl-C once, clear input. If the user types Ctrl-C twice then exit from the program. What I noticed is my Ctrl-C hook is invoked only the first time the user hits Ctrl-C. Am I missing something? Any comments or suggestions are welcome!
# Based on code from https://rosettacode.org/wiki/Handle_a_signal#Nim
import times, os, strutils
type EKeyboardInterrupt = object of CatchableError
proc handler() {.noconv.} =
raise newException(EKeyboardInterrupt, "Keyboard Interrupt")
setControlCHook(handler)
let t = epochTime()
var ctrl_c_caught = false
for n in 1..<int64.high:
try:
sleep 500
echo n
except EKeyboardInterrupt:
if ctrl_c_caught:
break
else:
ctrl_c_caught = true
echo "Program has run for ", formatFloat(epochTime() - t, precision = 0), " seconds."
The OS which tries to "block" signals while their handlers are executing. I think the raise inside the handler confuses your OS into thinking the handler has not yet returned. So, changing your handler to this works on Unix to unblock SIGINT, the Ctrl-C signal:
import posix
proc handler() {.noconv.} =
var nmask, omask: Sigset
discard sigemptyset(nmask)
discard sigemptyset(omask)
discard sigaddset(nmask, SIGINT)
if sigprocmask(SIG_UNBLOCK, nmask, omask) == -1:
raiseOSError(osLastError())
raise newException(EKeyboardInterrupt, "Keyboard Interrupt")
You probably also should re-call setControlCHook(handler) in your else clause after your echo. IIRC, the signal call Nim uses will (often but not portably!) just revert back to the default action which for SIGINT is to die (although, the way things sound like they are working for you, that reversion may never happen).
If you need something more portable then you might do better to manage the state from inside the signal handler rather than mixing signals & exceptions. E.g.:
import times, os, strutils
let t = epochTime()
var caught = false
proc handler() {.noconv.} =
if caught:
echo "Caught a second time"
quit()
caught = true
echo "Program has run for ", formatFloat(epochTime() - t, precision = 0), " seconds."
setControlCHook(handler)
for n in 1..<int64.high:
sleep 500
echo n
You could just add some if caught: setControlCHook(handler); clearInput() wherever you wanted to clear the input on that first Ctrl-C.
It might be possible to put the re-install of the signal handler in handler itself rather than externally in both cases, though I also would bet this is not portable/reliable/fully defined. POSIX tried to clean up all this signal stuff with sigaction and friends (which that sigprocmask is a part of), but Nim tries to only use ANSI C things and work both on Unix & Windows. So, the best answer to your question probably depends upon whether you seek cross-platform portability of Control-C handling or if Unix alone is good enough.