I was confused at first by jancheta's answer, until I discovered that the purpose of siglongjmp
is to unblock the received signal in the signal mask, before doing the jump. The signal is blocked at the entry of the signal handler so that the handler doesn't interrupt itself. We don't want to leave the signal blocked when we resume normal execution, and that's why we use siglongjmp
instead of longjmp
. AIUI, this is just shorthand, we could also call sigprocmask
followed by longjmp
, which seems to be what glibc is doing in siglongjmp
.
I thought it might be unsafe to do a jump because readline()
calls malloc
and free
. If the signal is received while some async-signal-unsafe function like malloc
or free
is modifying global state, some corruption could result if we were to then jump out of the signal handler. But Readline installs its own signal handlers which are careful about this. They just set a flag and exit; when the Readline library gets control again (usually after an interrupted 'read()' call) it calls RL_CHECK_SIGNALS()
which then forwards any pending signal to the client application using kill()
. So it is safe to use siglongjmp()
to exit a signal handler for a signal which interrupted a call to readline()
- the signal is guaranteed not to have been received during an async-signal-unsafe function.
Actually, that's not entirely true because there are a few calls to malloc()
and free()
within rl_set_prompt()
, which readline()
calls just before rl_set_signals()
. I wonder if this calling order should be changed. In any case the probability of race condition is very slim.
I looked at the Bash source code and it seems to jump out of its SIGINT handler.
Another Readline interface you can use is the callback interface. That is used by applications such as Python or R which need to listen on multiple file descriptors at once, for instance to tell if a plot window is being resized while the command line interface is active. They'll do this in a select()
loop.
Here is a message from Chet Ramey which gives some ideas of what to do to obtain Bash-like behavior upon receiving SIGINT in the callback interface:
https://lists.gnu.org/archive/html/bug-readline/2016-04/msg00071.html
The messages suggests that you do something like this:
rl_free_line_state ();
rl_cleanup_after_signal ();
RL_UNSETSTATE(RL_STATE_ISEARCH|RL_STATE_NSEARCH|RL_STATE_VIMOTION|RL_STATE_NUMERICARG|RL_STATE_MULTIKEY);
rl_line_buffer[rl_point = rl_end = rl_mark = 0] = 0;
printf("\n");
When your SIGINT is received, you could set a flag, and later check the flag in your select()
loop - since the select()
call will get interrupted by the signal with errno==EINTR
. If you find that the flag has been set, execute the above code.
My opinion is that Readline should run something like the above fragment in its own SIGINT handling code. Currently it more or less executes just the first two lines, which is why stuff like incremental-search and keyboard macros are cancelled by ^C, but the line isn't cleared.
Another poster said "Call rl_clear_signals()", which still confuses me. I haven't tried it but I don't see how it would accomplish anything given that (1) Readline's signal handlers forward the signal to you anyway, and (2) readline()
installs the signal handlers upon entry (and clears them when it exits), so they won't normally be active outside of Readline code.
printf
from a signal handler. – Jamajamaal