Hey
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
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
overFoundation
here - You can base your implementation on
AsyncStream
instead ofAsyncChannel
- 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.
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
.
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
FWIW this is similar to an idea I've been mulling about for IRQ handlers for embedded firmware in Swift.
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
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
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.