Signal handling in Swift

Hey :wave:
I just wrote my first swift package SignalHandler. So happy to share it with you all.
Feel free to point out corrections, suggestions and even collaboration on some Swift projects. Thanks :blush:

4 Likes

Hey @Genaro-Chris,

This is great! We have been talking about this in yesterdays SSWG meeting as well in relation with the service-lifecycle library.

In general, I agree that having an AsyncSequence that produces values for the signals is useful and I think this is something worth proposing to put into service-lifecycle.

I had a quick look at the implementation and here are a few unstructured notes:

  • I think you want to model this as a struct DispatchSignalAsyncSequnce: AsyncSequence
  • Would be good to use Dispatch over Foundation here
  • You can base your implementation on AsyncStream instead of AsyncChannel
  • I would recommend removing the custom operators. IMO they are very hard to discover and have significant overhead in compile time last that I checked.
6 Likes

To be clear, it's not just good to use Dispatch: the current code is not correct.

There are very few things you can safely do in a signal handler, and creating a Task is one of those things. The current implementation of notifier(value:) is definitely incorrect, so you'd need to move to DispatchSignalSource.

3 Likes

Can't really yield values from various sources. So the idea of using AsyncStream was ruled out.
Reference type was really needed here.
But anyways thanks for the corrections

Thanks, will try out that in the version 2.0

I'm using Swift 5.8 on linux and I can't use that DispatchSignalSource

It seems to me that Swift and other new systems programming languages
achieve safety by writing off a lot of essential phenomena in the
computer and operating system instead of providing abstractions that
avoid the pitfalls of C. A signal handler is just one of those
essential systems phenomena.

The C11 standard is fairly specific about the storage accesses you
can perform and the library functions that you can call from a signal
handler without invoking undefined or unspecified behavior. POSIX
is more relaxed, thank goodness, but it is still easy to write an
undefined POSIX program that uses signal handlers. If you reasonably
but incorrectly suppose that snprintf should be signal-safe and
use it in your signal handler, the compiler is likely to generate
instructions for the handler without complaint. Ditto for disallowed
storage accesses.

Swift does not seem to provide any facilities right now for handling
signals more safely than in C. In fact, there may be more pitfalls in
Swift than in C.

It would be wonderful if you could classify a function in Swift as a
signal handler and then the compiler enforced a restricted namespace,
(perhaps) restricted the available language primitives, and limited
the reachability of objects inside of the handler to those that were
signal-safe.

Dave

3 Likes

FWIW this is similar to an idea I've been mulling about for IRQ handlers for embedded firmware in Swift.

2 Likes

Yes! Interrupt service routines have the same issues as signal
handlers, and then some.

Years ago I was building some instrumentation for PCI-bus exceptions
that ran on an 80x86 non-maskable interrupt (NMI) in NetBSD. I found
that that environment is even thornier than an interrupt service routine
of the ordinary type because there were even fewer facilities to depend
on.

Swift programmers may benefit from restricting what virtually any
kind of callback function can do, no matter whether it is invoked
asynchronously (interrupt, signal) or not (AppKit enumeration callback).

My experience suggests that it would be useful if Swift could express
various families of restrictions on namespace, primitives, and data
reachability, and then apply families by name to functions.

Dave

3 Likes

swift-service-lifecycle is doing so, so it should be possible.

I don't follow this statement. An AsyncStream should really work here as the source of the AsyncSequence.

I can't really send value into a single AsyncStream from different asynchronous contexts :man_shrugging:

Why not? The AsyncStream.Continuation is thread safe and can be yielded to from different tasks/threads.

What I don't fully understand is why you would need to yield from different threads though.