Fold the made-up Addr<A> back into the pid, where it belonged. The typed
handle IS the pid now.
- RawPid { index, generation }: today's Pid, renamed — the raw numbers, the key
for identity-only plumbing and the heterogeneous monitor/link/pg tables.
- Pid<A = Erased>: RawPid + phantom actor type. Both an identity and a direct,
identity-bound address; when A: Addressable a send delivers A::Msg to exactly
this incarnation (next phase). Hand-written impls (eq/hash/fmt on the raw
only, no A bounds); fn()->A phantom keeps it Copy+Send+Sync for any A.
- Erased: uninhabited, deliberately NOT Addressable, so a typed send to a
Pid<Erased> won't compile — that's what send_dyn (section 4.6) will be for. It
is the default type arg, so every plain "Pid" written today still compiles as
Pid<Erased>; that, plus the fact that nothing destructures pid fields, is why
an identity change touching 18 files cascaded to zero internal edits.
- Addr<A> deleted; Name<M> / the Phase-2 registry unchanged (they key on raw).
Per the call to make the consumers take typed pids: the user-facing identity
APIs — monitor, link, unlink, request_stop, spawn_under(supervisor), pg::join,
pg::leave — are now generic over A and erase at the boundary; their bodies are
byte-for-byte unchanged. Pure plumbing (unpark, set_current_pid,
register_supervisor_channel) stays erased — it only ever sees internal identity.
Phase 1's Addr tests become Pid<A> tests (identity/copy/erase/Debug, Send+Sync).
Full suite + order-checker green, warning-free across all targets.
168 lines
7.2 KiB
Rust
168 lines
7.2 KiB
Rust
//! Process monitors.
|
|
//!
|
|
//! `monitor(target)` asks the runtime to deliver a single [`Down`] when
|
|
//! `target` terminates, and hands back a [`Monitor`] — the [`Receiver`] to read
|
|
//! it from, plus the identity (`id`, `target`) needed to take the registration
|
|
//! back down with [`demonitor`]. A monitor is:
|
|
//!
|
|
//! - **unidirectional** — the watcher learns of the target's death, but the
|
|
//! target learns nothing of the watcher, and the watcher is unaffected by
|
|
//! the death beyond the notification (contrast a *link*, which propagates
|
|
//! failure);
|
|
//! - **one-shot** — exactly one `Down` is ever sent for a given monitor.
|
|
//! The returned channel closes afterwards, so a second `recv()` yields
|
|
//! `Err(RecvError)`.
|
|
//!
|
|
//! This generalizes the older single-`supervisor_channel` mechanism: a
|
|
//! supervisor is just a hard-wired monitor that the parent installs at spawn
|
|
//! time. Here any actor may monitor any pid, any number of times.
|
|
//!
|
|
//! ## Reasons
|
|
//!
|
|
//! [`DownReason`] is deliberately payload-free. A panicking actor's payload
|
|
//! has a single owner and is delivered to whoever `join()`s the actor (as
|
|
//! `JoinError`); a monitor only learns *that* it panicked, not the value.
|
|
//! Monitoring a pid that is already gone (reclaimed, or never alive) yields
|
|
//! [`DownReason::NoProc`] immediately, mirroring Erlang's `noproc`.
|
|
//!
|
|
//! ## Demonitoring
|
|
//!
|
|
//! Each `monitor()` registration is tagged with a process-unique [`MonitorId`].
|
|
//! [`demonitor`] removes the registration named by a [`Monitor`] from its
|
|
//! target's slot, returning `Some(id)` if a live registration was found or
|
|
//! `None` if it had already fired (or the target is gone). Dropping the
|
|
//! [`Monitor`] afterwards discards any `Down` that the target had *already*
|
|
//! queued — the equivalent of Erlang's `demonitor(Ref, [flush])`.
|
|
//!
|
|
//! ## Races
|
|
//!
|
|
//! Registration (below) and `finalize_actor` (in `runtime`) both run under the
|
|
//! shared-state mutex, so a target that is still alive when its monitor is
|
|
//! registered is guaranteed to deliver a real `Down`; there is no window in
|
|
//! which the death slips between the liveness check and the registration.
|
|
//! `demonitor` is protected by the generation half of the pid: if the target
|
|
//! has died and its slot index been recycled, `slot_mut(target)` fails the
|
|
//! generation check and `demonitor` is a clean no-op — it can never strip a
|
|
//! *different* actor's monitor that happens to share the slot index.
|
|
|
|
use crate::channel::{channel, Receiver, Sender};
|
|
use crate::pid::Pid;
|
|
use crate::scheduler::with_runtime;
|
|
|
|
/// Why a monitored actor went down.
|
|
///
|
|
/// `Copy` because it carries no payload — see the module docs for why the
|
|
/// panic payload is *not* included here.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum DownReason {
|
|
/// The target returned normally.
|
|
Exit,
|
|
/// The target panicked. The payload is delivered to the actor's joiner,
|
|
/// not to monitors.
|
|
Panic,
|
|
/// The target was cooperatively cancelled via `request_stop`.
|
|
Stopped,
|
|
/// The target was already gone (finished and reclaimed, or never alive)
|
|
/// at the moment `monitor()` was called.
|
|
NoProc,
|
|
}
|
|
|
|
/// A monitored actor's termination notice.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub struct Down {
|
|
/// The pid that was being monitored.
|
|
pub pid: Pid,
|
|
/// How it went down.
|
|
pub reason: DownReason,
|
|
}
|
|
|
|
/// A process-unique identifier for one `monitor()` registration.
|
|
///
|
|
/// Opaque and `Copy`. Allocated from a monotonic counter in shared state, so
|
|
/// it is never reused for the lifetime of the runtime — distinct `monitor()`
|
|
/// calls on the same target get distinct ids, which is what lets [`demonitor`]
|
|
/// tear down exactly one of several monitors on a target.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
pub struct MonitorId(pub(crate) u64);
|
|
|
|
/// A live monitor: the receiving end of the one-shot [`Down`] channel, plus the
|
|
/// identity needed to [`demonitor`] it.
|
|
///
|
|
/// Read the notification from [`Monitor::rx`]. Not `Clone` (the receiver is a
|
|
/// single consumer). Dropping it closes the receiving end; if a `Down` was
|
|
/// already queued it is discarded with the channel.
|
|
pub struct Monitor {
|
|
/// This registration's process-unique id.
|
|
pub id: MonitorId,
|
|
/// The pid being monitored.
|
|
pub target: Pid,
|
|
/// The one-shot channel the `Down` arrives on.
|
|
pub rx: Receiver<Down>,
|
|
}
|
|
|
|
/// Monitor `target`. Returns a [`Monitor`] whose `rx` receives exactly one
|
|
/// [`Down`].
|
|
///
|
|
/// If `target` is still live, the `Down` arrives when it terminates. If
|
|
/// `target` is already gone, a [`DownReason::NoProc`] `Down` is queued
|
|
/// immediately so the caller's `rx.recv()` returns without parking.
|
|
pub fn monitor<A>(target: Pid<A>) -> Monitor {
|
|
let target = target.erase();
|
|
let (tx, rx) = channel::<Down>();
|
|
|
|
// Register under the target's cold lock. `tx.clone()` takes the channel's
|
|
// own lock — a Channel-class RawMutex, explicitly permitted *under* a Leaf
|
|
// (cold) lock by the lock order (see raw_mutex.rs). We must still not
|
|
// *send* under the lock, as `Sender::send` can unpark a parked receiver,
|
|
// and there's no reason to nest that.
|
|
let (id, registered) = with_runtime(|inner| {
|
|
let id = inner.alloc_monitor_id();
|
|
let registered = match inner.slot_at(target) {
|
|
Some(slot) => {
|
|
let mut cold = slot.cold.lock();
|
|
if slot.is_live_for(target) {
|
|
cold.monitors.push((id, tx.clone()));
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
None => false,
|
|
};
|
|
(id, registered)
|
|
});
|
|
|
|
if !registered {
|
|
let _ = tx.send(Down { pid: target, reason: DownReason::NoProc });
|
|
}
|
|
|
|
Monitor { id, target, rx }
|
|
}
|
|
|
|
/// Cancel the monitor `m`. Returns `Some(id)` if a live registration was found
|
|
/// on the target's slot and removed, or `None` if there was nothing to remove
|
|
/// — the target already fired its `Down` (the registration is drained on
|
|
/// finalize), was never alive (`NoProc`), or has been reclaimed.
|
|
///
|
|
/// This stops any *future* `Down`. To also discard a `Down` the target may have
|
|
/// *already* queued (the finalize-races-demonitor case), drop `m` afterwards;
|
|
/// dropping the [`Monitor`] closes its receiver and the queued notice goes with
|
|
/// it — the analogue of Erlang's `demonitor(Ref, [flush])`.
|
|
pub fn demonitor(m: &Monitor) -> Option<MonitorId> {
|
|
// Remove the registration under the target's cold lock, but move the
|
|
// `Sender` *out* and let it drop only after the lock is released:
|
|
// dropping the last sender runs `Sender::drop`, which may unpark a parked
|
|
// receiver — legal under a cold lock, but pointless to nest.
|
|
let removed: Option<(MonitorId, Sender<Down>)> = with_runtime(|inner| {
|
|
let slot = inner.slot_at(m.target)?;
|
|
let mut cold = slot.cold.lock();
|
|
if slot.generation() != m.target.generation() {
|
|
return None; // slot reused; the Down already fired
|
|
}
|
|
let pos = cold.monitors.iter().position(|(mid, _)| *mid == m.id)?;
|
|
Some(cold.monitors.remove(pos))
|
|
});
|
|
// `removed`'s sender drops here, outside the lock.
|
|
removed.map(|(id, _sender)| id)
|
|
}
|