6 ms·
Have you ever read up on communicating sequential processes? In that model you have one or multiple threads executing work while new messages arrive on a queue.
by fzeindl 3y ago
Have you ever read up on communicating sequential processes? In that model you have one or multiple threads executing work while new messages arrive on a queue. In such an architecture handling signals is trivial.
- boffinAudio 3y agoYeah, I was going to mention that if the long-running/CPU-bound work was put in a worker thread with a message queue in which state messages are received/handled, it'd be relatively trivial to handle the signal across these constraints, since signals are only delivered upon context switches. But if you've just got one very tight loop and are running like a bat out of hell, well you deserve the hell you're in. ;) longjmp() is definitely not the right way to do this. A thread dedicated to handling signals (or doing the CPU work) which properly tells the main() thread to die/quit/clean up is how I'd do it .. even if you only spawn a thread for signal handling, its still way cleaner than that longjmp() business .. which is very difficult to understand, even with the context you've given in this thread (I read your code - its nasty) ...
- gavinhoward 3y agoWhy do I deserve that for having a CPU-bound program? And why is longjmp() the wrong thing to do?
- boffinAudio 3y agoBecause signals are delivered on context switch, and you're not allowing any of that to happen. Its not a well-behaved process in that case .. not that you shouldn't be able to just burn as much CPU as you want, just that you would need to open up some space to communicate with the OS in the meantime, and having a signal-handler-thread or a cpu-working-thread is how to do it properly. (Do you also handle SIGHUP? SIGSTOP?) longjmp() is just asking for hell 6 months later when you come back and have to work out why you did such a hokey thing in the first place. EDIT: it should be noted that the "side-effect" of using longjmp is .. a context switch .. so you gave yourself some space to behave properly with the OS. Its just that the more future-proof way to do it is to have a thread for work (you have this already with main()) and a thread for signals (you'd just add this to service signals only and give main() a way to cleanly exit) ... Not too complex, and a tad bit easier to read 6 months later ..
- gavinhoward 3y agoUnless you include OS preemption in "context switch," you are wrong; signals can come at any time. If you do include it, then yes, signals only happen on context switch. But in that case, my CPU-bound code is not preventing context switches; the OS preempts it. This is how all major OS's are designed. If I didn't longjmp(), by the way, it could take an infinite amount of time between when the user sent SIGINT to when the program stopped by the very nature of what that utility does. By using longjmp(), a SIGINT becomes a true interrupt, which is what users want. Having a working thread and a signal thread does not change that because the signal thread would need to interrupt the working thread in the same way.
- boffinAudio 3y agoThey can come at any time but they are only delivered to your process by the kernel during a context switch. They're queued until then. > it could take an infinite amount of time between when the user sent SIGINT to when the program stopped by the very nature of what that utility does A well coordinated signal-handling-thread and a workload-thread won't manifest this issue - poorly managed threads however, will. >By using longjmp(), a SIGINT becomes a true interrupt, which is what users want. Hard disagree. What you've done is turned signals into interrupts, which is .. hokey. And not how signals are intended to be used. Its quite possible to get the behaviour you expect - fast interruption and death of work-code - but you'd have to sort your issues with threads out, first. EDIT: its decades-old proven technology: use a semaphore or a mutex to keep your threads in lockstep, and avoid this longjmp() malarkey... signals aren't hard, but maybe they're only just a little less harder than threads ..
- gavinhoward 3y agoI used the system you are advocating in my utility at first. The problem is that you need to constantly check for work. That is expensive on tight loops. Yes, I checked on every loop iteration unless I knew a small, but constant, amount of work had to be done. That's the thing: if you have a tight loop, do you know how long it's going to take to run through everything? In addition, when a SIGINT comes, can you be sure that your state is correct? Here's a test: download my utility and run these commands: $ git checkout 2.7.0 $ ./configure.sh -gO3 $ make $ echo "2^2^32" | bin/bc Then send a SIGINT after a random amount of time. I did that twice, and both times, I triggered asserts. Some state was wrong. This was after I had carefully gone through the entire codebase to check signals everywhere. Now do this: $ git checkout 6.7.0 $ ./configure.sh -gO3 -Sbc.sigint.reset $ make $ echo "2^2^32" | bin/bc And send a SIGINT after a random amount of time. bc exits because its stdin is a pipe, but nothing goes wrong. If you do it like this: $ bin/bc >>> 2^2^32 ^C interrupt (type "quit" to exit) 0: (main) ready for more input >>> And you can merrily continue. Semaphores and mutexes have the same problem: I would need to keep checking them. I understand why you think the way you do; I did too. But the real world is more complicated.
- gavinhoward 3y agoYes, I have. And that is what my framework defaults to. But when you must interrupt a CPU-bound operation, CSP does not work. CSP is an I/O design.