Draft
Conversation
Replaces the single-slot-per-signal design with multi-subscriber support, adds capability security via SignalAuth, and validates signal numbers through constrained types to prevent registration of fatal or uncatchable signals. Runtime changes (all three ASIO backends): - Atomic CAS-based synchronous subscriber registration prevents race conditions between subscribe and signal delivery - Subscriber arrays use lock-free slot allocation with spin-wait for concurrent first-subscriber setup - Unsubscribe remains async through the ASIO thread queue - Assert on subscriber overflow (MAX_SIGNAL_SUBSCRIBERS=16) Stdlib changes: - New SignalAuth capability primitive (derived from AmbientAuth) - New ValidSignal/MakeValidSignal constrained types with platform- specific signal whitelists - SignalHandler requires SignalAuth and ValidSignal - SignalRaise requires SignalAuth (accepts raw U32 since raising fatal signals is legitimate) - ANSITerm constructor takes SignalAuth parameter This is a feasibility prototype for the RFC, not a final implementation.
Multi-type PR (Added + Changed) requires manual CHANGELOG and next-release.md updates rather than individual release note files.
The double-raise and 30-second timeout were workarounds for running tests concurrently. The Makefile runs stdlib tests with --sequential, so a single raise and standard 10-second timeout are sufficient.
…upport Three issues: 1. _is_usr2 logic was inverted — it called Sig.usr2() when scheduler_scaling_pthreads WAS set (triggering compile_error on macOS CI) instead of when it was NOT set. 2. CI runs debug tests under lldb which only passes SIGINT through (process handle SIGINT --pass true --stop false). Tests using SIGTERM/SIGURG/SIGALRM caused lldb to stop the process. Changed all tests to use SIGINT since sequential execution prevents interference. 3. Added Windows branch to _is_handleable — Windows signal() only supports SIGINT and SIGTERM as catchable signals.
Actor constructors run asynchronously in Pony. When the test creates s1 and s2 then calls s1.raise(), s2's constructor may not have executed yet — so s2 isn't in the subscriber array when the signal fires. Having each handler raise its own signal guarantees that handler's constructor has completed (same-actor message ordering) before the raise executes, ensuring it's subscribed when the signal arrives.
The double-raise is a test technique, not an API workaround.
SeanTAllen
commented
Mar 9, 2026
Comment on lines
+74
to
+77
| match MakeValidSignal(Sig.winch()) | ||
| | let sig: ValidSignal => | ||
| SignalHandler(auth, recover _TermResizeNotify(this) end, sig) | ||
| end |
Member
Author
There was a problem hiding this comment.
this should never fail but in actual final version we'd want to have an "Unreachable" here.
Member
Author
|
Open issue: subscriber overflow The current implementation uses a fixed-size array of 16 subscriber slots per signal. In debug builds, exceeding the limit triggers an assert crash. In release builds, the subscriber is silently dropped — the handler never receives notifications. Neither outcome is acceptable. This needs to be replaced with a dynamically growable structure. The challenge is that the subscriber array is accessed from signal context (epoll) or the ASIO thread (kqueue, IOCP) with lock-free atomics, so the growth mechanism needs to be safe in that context. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces the single-slot-per-signal design with multi-subscriber support, adds capability security via
SignalAuth, and validates signal numbers through constrained types to prevent registration of fatal or uncatchable signals.Added:
SignalAuthcapability primitive (derived fromAmbientAuth) for signal operationsValidSignal/MakeValidSignalconstrained types with platform-specific signal whitelistssignalsexample program demonstrating the redesigned APIChanged (breaking):
SignalHandler.createnow requiresSignalAuthandValidSignalparametersSignalRaise.applynow requiresSignalAuthANSITerm.createnow requiresSignalAuthas its first parameter —ANSITerminternally creates aSignalHandlerfor SIGWINCH to detect terminal resizes, so it needs signal authority. Any program usingANSITerm(includingReadline) must pass aSignalAuth.Runtime changes across all three ASIO backends (epoll, kqueue, IOCP):
This is a feasibility prototype for the RFC — it may end up being the final implementation.