Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .release-notes/next-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,59 @@ Cross-compilation to Linux targets (RISC-V, ARM, ARMhf) now uses the embedded LL

The embedded LLD path activates automatically when cross-compiling to a Linux target without `--linker` set. To use an external linker instead, pass `--linker=<command>` as an escape hatch to the legacy linking path.

## Add capability security and multi-subscriber support to signal handling

The `signals` package now provides capability security and signal number validation. `SignalHandler` requires a `SignalAuth` capability (derived from `AmbientAuth`) and a `ValidSignal` constrained type that enforces platform-specific whitelists, preventing registration of fatal signals like `SIGSEGV` or uncatchable signals like `SIGKILL`. Multiple actors can now subscribe to the same signal — up to 16 subscribers per signal number, with all subscribers notified when the signal fires.

```pony
use "constrained_types"
use "signals"

actor Main
new create(env: Env) =>
let auth = SignalAuth(env.root)
match MakeValidSignal(Sig.int())
| let sig: ValidSignal =>
let handler = SignalHandler(auth, MyNotify, sig)
end
```

## Signal handling API requires `SignalAuth` and `ValidSignal`

The `SignalHandler` constructor now requires a `SignalAuth` capability and a `ValidSignal` constrained type instead of a raw `U32` signal number. `ANSITerm.create` also requires a `SignalAuth` parameter as its first argument.

Before:

```pony
use "signals"

let handler = SignalHandler(MyNotify, Sig.int())
```

```pony
use "term"

let term = ANSITerm(handler, env.input)
```

After:

```pony
use "constrained_types"
use "signals"

let auth = SignalAuth(env.root)
match MakeValidSignal(Sig.int())
| let sig: ValidSignal =>
let handler = SignalHandler(auth, MyNotify, sig)
end
```

```pony
use "signals"
use "term"

let auth = SignalAuth(env.root)
let term = ANSITerm(auth, handler, env.input)
```

3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ All notable changes to the Pony compiler and standard library will be documented

- Compile-time string literal concatenation ([PR #4900](https://github.com/ponylang/ponyc/pull/4900))
- Add `--sysroot` option for cross-compilation ([PR #4964](https://github.com/ponylang/ponyc/pull/4964))
- Add capability security and multi-subscriber support to signal handling ([PR #4984](https://github.com/ponylang/ponyc/pull/4984))

### Changed

- Update to LLVM 21.1.8 ([PR #4876](https://github.com/ponylang/ponyc/pull/4876))
- Exempt unsplittable string literals from line length rule ([PR #4923](https://github.com/ponylang/ponyc/pull/4923))
- Use embedded LLD for cross-compilation to Linux targets ([PR #4964](https://github.com/ponylang/ponyc/pull/4964))
- Signal handling API requires `SignalAuth` and `ValidSignal` ([PR #4984](https://github.com/ponylang/ponyc/pull/4984))
- `ANSITerm` constructor requires `SignalAuth` parameter ([PR #4984](https://github.com/ponylang/ponyc/pull/4984))

## [0.61.0] - 2026-02-28

Expand Down
6 changes: 5 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ Floods a single `Receiver` actor with messages from multiple senders to demonstr

Establishes a TCP connection and applies explicit backpressure when the send buffer fills up. Demonstrates the `backpressure` package's `Backpressure` primitive, `ApplyReleaseBackpressureAuth`, and throttled/unthrottled callbacks for detecting when the scheduler responds to pressure.

## File I/O and Terminal
## File I/O, Terminal, and Signals

### [files](files/)

Expand All @@ -102,6 +102,10 @@ Reads a file specified as a command-line argument and prints its path and conten

An interactive command-line prompt with tab completion and command history. Demonstrates the `term` package's `Readline` and `ReadlineNotify` interfaces, and `Promise`-based prompt control where rejecting the promise exits the loop.

### [signals](signals/)

Registers two handlers for SIGINT, raises the signal programmatically, and disposes one handler. Demonstrates the `signals` package's capability-secured API: `SignalAuth` for authorization, `MakeValidSignal` for signal validation via constrained types, `SignalHandler` for subscription, and `SignalNotify` for callbacks. Shows that multiple handlers can subscribe to the same signal.

## C FFI

### [ffi-callbacks](ffi-callbacks/)
Expand Down
4 changes: 3 additions & 1 deletion examples/readline/main.pony
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use "signals"
use "term"
use "promises"

Expand Down Expand Up @@ -44,7 +45,8 @@ actor Main
env.out.print("Use 'quit' to exit.")

// Building a delegate manually
let term = ANSITerm(Readline(recover Handler end, env.out), env.input)
let term = ANSITerm(SignalAuth(env.root),
Readline(recover Handler end, env.out), env.input)
term.prompt("0 > ")

let notify = object iso
Expand Down
50 changes: 50 additions & 0 deletions examples/signals/main.pony
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use "constrained_types"
use "signals"

class Handler is SignalNotify
let _env: Env
let _name: String

new iso create(env: Env, name: String) =>
_env = env
_name = name

fun ref apply(count: U32): Bool =>
_env.out.print(_name + " received signal (count: " + count.string() + ")")
true

fun ref dispose() =>
_env.out.print(_name + " disposed")

actor Main
"""
Demonstrates the signals package:
- Creating a SignalAuth capability from AmbientAuth
- Validating signal numbers with MakeValidSignal
- Registering multiple handlers for the same signal
- Raising a signal programmatically
- Disposing a handler

Run the program and press Ctrl-C to send SIGINT, or wait for the
programmatic raise to fire both handlers.
"""
new create(env: Env) =>
let auth = SignalAuth(env.root)

match MakeValidSignal(Sig.int())
| let sig: ValidSignal =>
let h1 = SignalHandler(auth, Handler(env, "handler-1"), sig)
let h2 = SignalHandler(auth, Handler(env, "handler-2"), sig)

env.out.print("Two handlers registered for SIGINT.")
env.out.print("Raising SIGINT programmatically...")
h1.raise(auth)

// Dispose handler-2 to show cleanup
h2.dispose(auth)
| let f: ValidationFailure =>
env.out.print("Failed to validate SIGINT")
for e in f.errors().values() do
env.out.print(" " + e)
end
end
7 changes: 6 additions & 1 deletion packages/bureaucracy/custodian.pony
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ actor Custodian

```pony
use "bureaucracy"
use "constrained_types"
use "signals"

actor Actor1
Expand All @@ -35,7 +36,11 @@ actor Custodian
custodian(actor2)
custodian(actor3)

SignalHandler(TermHandler(custodian), Sig.term())
let auth = SignalAuth(env.root)
match MakeValidSignal(Sig.term())
| let sig: ValidSignal =>
SignalHandler(auth, TermHandler(custodian), sig)
end

class TermHandler is SignalNotify
let _custodian: Custodian
Expand Down
Loading