
Sigaction: see who killed you, and more - tbodt
http://rachelbythebay.com/w/2018/04/16/signal/
======
geocar
There's a lot of cool kings you can do with sigaction.

Trapping sigsegv is a _great way_ to do memory management.

    
    
        void sa(int _,siginfo_t*s,void*__){
          mmap((void*)((~PAGESZ-1)&(uintptr_t)s->si_addr),PAGESZ,
            PROT_READ|PROT_WRITE,MAP_FIXED|MAP_ANONYMOUS,-1,0);
        }
    

Want automatic logging of a big on-disk data structure? mmap some pages read-
only map_private, trapping writes and logging the address (then remapping the
page read+write). Then at "save time", write out page contents to a replay
log, fsync+fdatasync+fcntl-F_FULLFSYNC, then rewrite the file using
pwrite/write.

You can also implement big sparse hashmaps this way: Every time a server gets
a request, it allocates a cell and tells the client about it. Those cells are
read+write. If a cell gets checked in, the server simply decrements the
counter, but if there's a page fault (read-only) the server can then take the
slower path of finding the server that actually responds to it.

And so on.

~~~
wahern
I once implemented a specialized contiguous stack structure that used this
trick to automatically reallocate the stack. (The stack was state for a
compiler-generated backtracking NFA.) The handler would longjmp to code that
realloc'd the stack and restarted the NFA. (No internal pointers so resetting
the NFA state and restarting was trivial.) I forget the exact performance
improvement, but compared to the original code with explicit boundary checks I
think it was several multiples faster.

Unfortunately signal handlers are process-wide. These tricks are a bad idea
for code that needs to play nice with other code (i.e. libraries, modules,
etc) unless you know the process won't be multithreaded or you can own
SIGSEGV.

In the above hack I used sigaltstack, which is thread-local, to communicate
per-thread state to the handler, allowing the scheme to work multi-threaded
and to detect unintended faults. (The alt stack memory region used special
page-aligned offsets so I could derive a thread-local object that didn't
overlap with the signal stack.) Still, such a scheme is too ugly to hide
inside a blackbox component which is why these tricks rightly don't see much
usage. Your code and my code could have never run in the same process.

~~~
jcranmer
> Unfortunately signal handlers are process-wide. These tricks are a bad idea
> for code that needs to play nice with other code (i.e. libraries, modules,
> etc) unless you know the process won't be multithreaded or you can own
> SIGSEGV.

It's safe to handle processor trap-caused signals, since those are guaranteed
to be delivered to the current thread. (SIGSEGV, SIGILL, SIGFPE, SIGBUS,
SIGTRAP). The trickier part is that you can still send SIGSEGV and the like
from another process, which means you have to distinguish between "is this
signal caused by the processor or somebody else?" not to mention the general
"is this signal in the code where I should handle it."

Of course, a saner OS (like Windows) would give you a more stack-based
approach to handling hardware traps, so that a function can say "if I, or
anything I call, gets a #DE, deliver SIGFPE to this location."

~~~
wahern
But an installed signal _handler_ is process-wide. There's no way for a thread
to install a signal handler without potentially stepping on the toes of some
other thread wanting to install its own handler. Contrast that with signal
masks which are thread-local, permitting libraries to set and restore masks
without disturbing global process state.

~~~
jcranmer
If you code your signal handler right, you could dispatch the signals you
don't want to handle to another signal handler.

I'm not arguing that the POSIX signals API is in any way sane (it's not). It
is _possible_ to build signal handlers correctly, but it is far from easy and
certainly far harder than it has any right being.

~~~
wahern
Yes, but my point was that these tricks don't work well with libraries, which
don't normally (if ever) expose a public API for chaining signal handlers,
often couldn't do so as a practical matter, anyhow--see my example, below.

If you control the entire process and all the code, then sure. But even then
one should avoid deviating from idiomatic, modular programming patterns
without very good reason. Because signal handlers are process-wide they're
global state; _sharing_ global state is not a friendly path when it comes to
code maintenance and long-term project management.

Not too long ago both OpenBSD and macOS lacked sigtimedwait(), so for my Lua
Unix module I wrote a "portable" implementation by longjmp'ing from a SIGALRM
handler. It worked well and was correct but with the caveat that it shouldn't
be used from multithreaded programs. I usually try to avoid multithreaded
programming myself, but others embrace it. As both a user and author of
libraries, caveats like this are a real PITA.

------
quotemstr
Signal handlers don't _have_ to be process-wide. See my detailed proposal for
fixing the situation here: [https://www.facebook.com/notes/daniel-
colascione/toward-shar...](https://www.facebook.com/notes/daniel-
colascione/toward-shareable-posix-signals/10157129032641102/)

The trouble is that nobody is actually interested in fixing the core API. When
I've raised the issue (and my proposal) with libc maintainers, the response
has been a bizarre insistence that signals are somehow illegitimate, that the
things people do with signals are wrong, and that everyone should just stop
with signals despite there being no viable replacement mechanism.

As a result of this head-in-the-sand attitude, we're left with an awful
decades old signals API and an absolute mess of ad-hoc libraries (which
conflict with each other) to work around the worst of the problems.

This is how good systems ossify and eventually die.

~~~
carterschonwald
My limited knowledge on this topic seems to indicate that some signals are per
thread in terms of handler registration rather than process wide. At least if
you register the handler in that thread. Am I incorrect ?

~~~
quotemstr
Signals are process wide. There is no confusion on this subject; people who
claim there is are probably thinking of the delivery of process-directed
signals, which is an entirely different subject. There is no current system
for which sigaction acts per thread.

------
nrclark
Sigaction is pretty cool. Signals still suck though, if you need to stick to
the POSIX specs.

It's crazy that so many standard library functions will fail with an EINTR
errno if your program gets a signal while it's in a library function.
Basically any library call that ever does any I/O of any kind (that means
common stuff like open/dup/read/write, but also less obvious stuff like
getpwuid).

Sigaction provides the SA_RESTART flag which _should_ auto-restart most calls,
but it doesn't work everywhere. And the functions which do and don't auto-
restart aren't documented anywhere that I've ever found.

As a result, any high-reliability program that installs signal-handlers
basically needs to have a retry mechanism for every libc call that can result
in an EINTR. Some functions might not need it, but I've never found a good way
to tell which ones do and which ones don't. Select(), for example, doesn't
support SA_RESTART. Neither does wait(). And that means they all need to be
checked every time, if you're concerned with portability and reliability. And
that's nuts.

IIf I could change one thing about sigaction in POSIX, it would be to add an
SA_NOINTR flag to the sigaction API, which would let any POSIX library calls
finish before presenting a signal.

That, plus I'd also add Linux's signalfd() to the POSIX standard so that we
could do away with "this one weird trick from a DJB" that everybody uses now
(and I'd make signalfd use the new SA_NOINTR).

~~~
iforgotpassword
Agreed. Years ago I wrote a daemon for Linux with glibc only, so I assumed I
can just ignore EINTR everywhere, until I needed to use valgrind and suddenly
had EINTRs everywhere.

Rather than making signalfd POSIX, if we play make-a-wish let's just go for
kqueue. Signalfd feels somewhat hackish, even though its simplicity admittedly
has some appeal too.

~~~
xenadu02
Signals are an example of false simplicity: they themselves are very simple at
the cost of foisting the complexity on you and every library you use.

