SIGINT is really designed for interactive applications. Most processes should simply treat it like a SIGTERM unless they have some sort of REPL. Unless you need graceful shutdown, most processes shouldn't mask either signal. If they do, the polite thing is to unmask after receiving the first signal so subsequent signals immediately terminate.
With an important productivity app like rogue(6) there is (probably) only one process in the foreground process group, and curses has (probably) set the terminal to have control+c either ignored or delivered to the rogue process as a key event. The player probably does not want to have their rogue process vanish because they hit control+c by habit, like trek(6) likes to do, but someone wrote blocksig(1) as an exec wrapper so that SIGINT can be ignored. With a complicated shell pipeline, the player probably does want the whole thing to go away, but that may be difficult depending on exactly how complicated the shell pipeline is and whether any processes in that pipeline are fiddling with the global terminal state. (Global to the process groups and their PIDs under that particular terminal, not the whole system or universe or whatever. But global enough to cause problems.)
Opinions also vary here, some want that LISP image to never go away (those emacs users, probably), others may want control+c to murder the LISP image so they can go back to hacking in vi. POSIX probably says something about shell pipelines and control+c and such, which various shells may or may not follow, exactly. Etc...
And you probably know that the author of curses is the author of rogue!
> it's not rocket science to handle it in a sane fashion even in a multi-threaded application. Modern languages make this trivial. The author makes it sound like some dark art
Which language? I'll specify one so we can begin the process of picking each apart. Python? There is a sibling thread indicating Python issues. I don't know what the actual internal status is with Python signal handling but I am guessing the interpreter actually doesn't handle it correctly if I spent any time digging. Do you mean apps implemented in Python? They will almost certainly not be internally data-consistent. Exposing a signal handling wrapper means very little particularly when they frequently do this by ignoring all of the bad implications. I just checked Python's docs, and not surprisingly, Python guarantees you'll be stuck in tight loops: https://docs.python.org/3/library/signal.html That's just one gotcha of many that they probably aren't treating. This dialogue is going to play out the same way regardless of which language you choose.
Do you mean Postgres? I haven't used it recently but the last comment I read on HN seemed to indicate you needed to kill it in order to stop ongoing queries in at least some situations. If by a stroke of luck it does support SIGINT recovery (which would be great), what about the hundreds of other db applications that have appeared recently? You can't just call the signal handler wrapper and declare victory.
I've done plenty of signal handling in Python and it's extremely straight forward. Like other languages, the runtime takes care of safely propagating information from the signal handler to other execution contexts, which requires being careful in a language like C (it's not hard, but you can't be naive). I wouldn't be surprised if there were bugs in Python, it's a mess generally and I'm not a fan.
Postgres queries run as subprocesses. You can send them any signals you want. Postgres tries very hard to be durable, and it handles signals carefully but often to the dismay of the operator who can't force it to stop without SIGKILL.
> registering for a signal is arguably non-trivial and incorrectly specified in many places since sigaction() supersedes signal().
This isn't a good argument, no one uses signal(2), I'm not aware of that ever being recommended in recent history and even the docs on my system scream "never use this" quite clearly.
Look, if you're not going to read the docs, signal handling will be the least of your concern. Signal behavior is extremely well documented.
It's not, though you need to be careful if you want to exit cleanly -- you can't just exit() or _exit(). You have to get all the threads to exit, and that requires a global volatile sig_atomic_t flag and some way to signal all threads that might be sleeping in event loops or what have you.
Threads waiting on event loops is exactly what you want on shutdown: that's what you use to notify them to exit.
Which are the applications the article is talking about anyway.
Python registers POSIX signal handlers, that upon SIGTERM/SIGINT, set a flag. Next time the interpreter loop runs, the flag is checked before the next instruction is being run and the stack is unwinded.
When you call out to some C code, that C code may run for a long time. During that time, there is no interpreter loop actually looping. Therefore, all signals are ignored in principle while C code runs.
It's possible for Python code to become uninterruptible while it is calling something like pthread_join.
See https://stackoverflow.com/questions/39930722/how-do-i-catch-...
Then of course, you have that on top of all the other problems mentioned by the blogpost.
Single-threaded Python handles it well - as long as you don't register a custom signal handler, Ctrl-C raises a KeyboardInterrupt exception immediately. KeyboardInterrupt is not a subclass of Exception, (it inherits from SystemExit, which inherits from BaseException directly), so any "except Exception:" clauses don't catch it. Which is the intent. This is also a primary reason to never use bare "except:" clauses (it will prevent Ctrl-C from working!).
For multithreaded Python, the easiest thing to do is just mark all your threads as daemon=True, so they die if the main thread exits. When you can't do that, the best bet is some "threading.Event" and custom SIGINT handler that triggers the event. I kind of wish SIGNINT by default would raise the KeyboardInterrupt in all threads, but I'm sure there are good reasons not to.
Maybe I'm missing something here but… so what? If at the end of your Ctrl+C signal handler you exit() as expected, then the OS will clean up your process's memory anyway.
> It definitely applies to interpreters, database-style terminal interfaces, REPLs, consoles, calculators, command-lines, and other categories I've unintentionally left out.
So if your article is supposed to be exclusively about those, I'd suggest you make this clear right in the beginning.
That's not what Ctrl+C is meant for or used for. It's used to terminate the running application, not the running task within that application.
If you want to be able to "resume your work" then you should press Ctrl+Z.
If you want something else then the application should probably be listening for some other keystroke. "Catch Ctrl+C and do something else" is a pretty awful idea for the very reason mentioned at the top of TFA (when you press Ctrl+C, it's to get out of whatever you're stuck in, so that you don't have to go open another terminal and type in killall ...)
exit() is not signal-safe; signal handlers are expected to call quick_exit() or _Exit() instead.
I'm having trouble, however, thinking of programs to which this applies. I just scrolled through my shell history, and the most common interactive program I've used in my history file is a debugger, which handles killing the active program correctly with no issues, followed by resource monitoring applications, shells, etc.
Can somebody tell me an example command-line application where there's a high degree of interactivity but is also multithreaded, has DB consistency guarantees, network requests in-flight, etc? I'm genuinely having trouble thinking of anything that's not a REPL or vim/emacs.
He would help his case by giving specific examples of problematic programs.
* https://www.mit.edu/people/dpolicar/writing/prose/text/think...
(For those who don't know "kill KILL" is equivalent to "kill -9". And despite the name "kill" is a tool for sending signals to processes.)
Proper handling of SIGINT/SIGQUIT: https://www.cons.org/cracauer/sigint.html
What I've mostly seen in programs is a clean exit from the running application, if live user input is not intended to be used. Clearing a line or something similar like redrawing the terminal (that's mostly Ctrl-L though) is what interactive programs do, let's say shells or ncurses UI programs.
Whenever I made some hobby scripts that exit cleanly when receiving a SIGINT, I've made a global counter of interrupts. When SIGINT is received, the counter is incremented, which tells the main loop to stop as soon as possible. But if this counter exceeds three signals, the application would exit immediatley. This may not be ideal, but CTRL-C CTRL-C CTRL-C is easier than kill -9 `pgrep a.out`.
Like the top comment says, expecting a concrete and general behaviour on different types of software for such a broad signal doesn't gain wide approval.
What "return of control" did the author mean, on what kinds software?
Good explanation here of shutdown hooks: https://www.baeldung.com/jvm-shutdown-hooks
The only software I can think of that could "restart" after a Ctrl+C is usually daemons or other long-lived processes (which already need to be able to "restart" after any kind of shutdown and thus have significant amounts of code dedicated to serializing and unserializing their internal state).
TFA even goes so far as to talk about memory leaks - which are completely irrelevant when your process is about to exit anyway!
I think of it this way: we have both SIGINT and SIGTERM for a reason. One "interrupts" and the other "terminates" and there are often good reasons to handle "interrupt" differently from "terminate" -- at least in interactive programs.
Open a Ruby interpreter (`irb`). Type `i=0; loop { i += 1 }`. Press Ctrl+C.
* Irb is still running.
* Your infinite loop has been stopped.
Type `i`:
* The REPL state preserved as much progress as it could when you aborted the run.
Now do the same thing in `sh`. Now `python`. Now `psql`. All handle Ctrl+C in the way the article mentioned!
Respectfully, you seem to be confusing SIGINT, which is an interrupt signal and SIGTERM, which is a terminate signal. Many processes interpret SIGINT in a way which is indistinguishable from SIGTERM, but others do not (e.g. most REPLs).
I think it is a Java (or more to the point JVM) program, not sure if that has anything to do with it. In addition I believe it is a lot of parallel programs running at once, or they could be different threads. As there are lots of Firebase services it needs to emulate.
it shows the current line number in nano.
etc.
The project in question is my `bc` [1].
Until version 3.0.0 [2], it used a "yield" architecture: every loop it could enter had a check for a signal. This got tedious, so I decided to make the jump to instant-ish reset.
I was lucky in several ways. First, `bc` is a really good program to reset; you just stop it executing, wipe all data away, and ask for more input with a blank slate. Second, it is single-threaded.
Nevertheless, it was still really difficult, especially to have no memory leaks.
First, I had to learn how to use `sigsetjmp()` and `siglongjmp()`. Yep, that was how I was going to do this. Once I learned, I implemented a stack of `sigjmp_buf`'s. Then, when a signal happens, each individual `sigjmp_buf` is used. This allowed me to properly free memory on the way.
In essence, if a function had allocated memory, then it would push a `sigjmp_buf` on the stack, and then when a `siglongjmp()` happened, execution would go to a label where that memory would be freed before continuing the jump series.
Then I implemented signal locks. It is safe to `siglongjmp()` out of signal handler, as long as it didn't interrupt code that was non-async-signal-safe. So I used signal locks for that, and when "unlocking" the lock, it would check for a signal and jump. And if the signal handler sees a lock, it just sets a flag and returns.
Then I had to go through my codebase and protect every bit of non-async-signal-safe code with locks. It was tedious, but the result is fantastic.
Edit: I forgot to add that there is more information at [3] and [4].
Nowadays, I'm working on a threaded build system, and when it gets SIGINT, it sends a message to threads to stop as soon as their children are done. If it receives a second, it just exits.
So yeah, every application is different, but it is possible.
[1]: https://git.yzena.com/gavin/bc
[2]: https://git.yzena.com/gavin/bc/src/branch/master/NEWS.md#3-0...
[3]: https://git.yzena.com/gavin/bc/src/branch/master/manuals/dev...
[4]: https://git.yzena.com/gavin/bc/src/branch/master/manuals/dev...
I was calling this "responsive idempotence" when discussing how the GNU coreutils are tested:
Short cut here, ctrl-z to background the process, then kill -9 %1 to kill the first job (type jobs for the numbers)
Crtl-C (SIGINT), as far as I know, was used to "gracefully terminate" interactive process from day zero of Unix. I cannot find any use in that of what author proposes: suspend execution by sending SIGINT, but then what ? Get to some process built-in debugging shell ? Isn't that what GDB was made for ?
Non-interactive programs do not need any special handling for SIGINT, and that seems to be what you're talking about. The author was talking about interactive programs like irb, bc, bash, gdb, and python, all of which behave as they desire, returning you to their REPL prompt upon receipt of SIGINT. One example of an interactive program that does not have the desired behavior is GNU Units.