Skip to main content

reverie/
process_signal_control.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 * This source code is licensed under the BSD-style license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8//! Run-scoped process signal publication and scheduler-selected delivery.
9//!
10//! These operations do not borrow a Guest, resume instructions, or run a Tool
11//! hook. Installation is atomic and precedes the first guest callback.
12
13use std::fmt::Debug;
14use std::sync::Arc;
15
16use serde::Deserialize;
17use serde::Serialize;
18
19use crate::CallbackSignalSite;
20use crate::ExitStatus;
21use crate::ProcessAlarmSignalDisposition;
22use crate::SignalEvent;
23use crate::SignalProcessId;
24use crate::SignalTaskIdentity;
25use crate::syscalls::Errno;
26
27/// Whether the Tool takes responsibility for recipient selection.
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub enum BackendSignalControlMode {
30    /// Preserve the backend's existing selection behavior.
31    Unchanged,
32    /// Publication and return-to-user selection use the installed control.
33    ToolControlled,
34}
35
36/// An actual process-pending publication; it says nothing about recipient masks.
37#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
38pub struct ProcessSignalPublication {
39    /// Exact lifetime whose shared pending queue committed the operation.
40    pub process: SignalProcessId,
41    /// Disposition/pending generation, not a delivery counter.
42    pub pending_generation: u64,
43    /// Whether the first pending event already occupied this standard signal.
44    pub coalesced: bool,
45    /// Disposition observed at publication, before any Tool filtering.
46    pub disposition: ProcessAlarmSignalDisposition,
47}
48
49/// Publication errors retain the boundary between no effect and committed effect.
50#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
51pub enum ProcessSignalPublicationResult {
52    /// No pending state or readiness changed.
53    RejectedBeforeCommit(Errno),
54    /// Shared pending state and readiness committed.
55    Committed(ProcessSignalPublication),
56    /// The pending operation committed but readiness failed. Never retry it.
57    FailedAfterCommit {
58        /// Committed effect.
59        receipt: ProcessSignalPublication,
60        /// Original readiness failure.
61        errno: Errno,
62    },
63}
64
65/// A scheduler-authorized terminal child transition.
66///
67/// Both process identities include their run-local generations, so a reused
68/// numeric PID cannot inherit this completion. Times are Linux clock ticks,
69/// not nanoseconds.
70#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
71pub struct ChildExitCompletion {
72    /// Exact parent process lifetime receiving SIGCHLD.
73    pub parent: SignalProcessId,
74    /// Exact terminal child process lifetime.
75    pub child: SignalProcessId,
76    /// Complete wait status, including signal and core-dump provenance.
77    pub status: ExitStatus,
78    /// Whether the terminal status remains consumable by a wait syscall.
79    pub waitable: bool,
80    /// Virtual child uid reported through siginfo.
81    pub uid: u32,
82    /// Child user CPU time in signed Linux clock ticks.
83    pub user_ticks: i64,
84    /// Child system CPU time in signed Linux clock ticks.
85    pub system_ticks: i64,
86}
87
88/// Effect committed by one child-completion publication.
89#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
90pub enum ChildExitPublicationEffect {
91    /// SIGCHLD entered the shared process-pending set.
92    Queued,
93    /// SIGCHLD was already pending and retained its first complete siginfo.
94    Coalesced,
95    /// An explicit `SIG_IGN` suppressed SIGCHLD generation.
96    SuppressedExplicitIgnore,
97    /// A later ignored-disposition transition discarded the generation in
98    /// which this child event was authorized before delayed publication ran.
99    DiscardedByDispositionChange,
100}
101
102/// Receipt for an irreversible child-completion publication.
103#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
104pub struct ChildExitPublication {
105    /// Exact completion whose effect committed.
106    pub completion: ChildExitCompletion,
107    /// Disposition/pending generation at the operation.
108    pub pending_generation: u64,
109    /// Whether publication queued, coalesced, or explicitly suppressed SIGCHLD.
110    pub effect: ChildExitPublicationEffect,
111}
112
113/// Complete result of publishing one scheduler-authorized child completion.
114#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
115pub enum ChildExitPublicationResult {
116    /// No pending state or signalfd readiness changed.
117    RejectedBeforeCommit(Errno),
118    /// The receipt's effect committed.
119    Committed(ChildExitPublication),
120    /// The effect committed before readiness failed. Never retry it.
121    FailedAfterCommit {
122        /// Retained irreversible publication receipt.
123        receipt: ChildExitPublication,
124        /// Original readiness failure.
125        errno: Errno,
126    },
127}
128
129/// One eligible task in an authoritative, process-transaction snapshot.
130#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
131pub struct SignalRecipient {
132    /// Exact live task, independent of numeric TID reuse.
133    pub task: SignalTaskIdentity,
134}
135
136/// Authorization for one task's actual return-to-user selection.
137///
138/// The backend registers this permit before the Tool releases its callback.
139/// Copying the value does not create another registered permission.
140#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
141pub struct SignalDeliveryPermit {
142    /// Exact process/task lifetime.
143    pub task: SignalTaskIdentity,
144    /// Run-local scheduler choice identity.
145    pub sequence: u64,
146    /// Parked syscall callback, or an ordinary user-return boundary.
147    pub site: Option<CallbackSignalSite>,
148}
149
150/// Actual completion of a permitted boundary, before guest entry.
151#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
152pub enum SignalBoundaryOutcome {
153    /// A caught handler's frame, registers and mask are committed.
154    Caught,
155    /// No handler was installed; no interrupted wait may be invented.
156    NoHandler,
157    /// A committed guest exit, before physical worker joins or consuming hooks.
158    /// The permit supplies the exact process/task lifetime; an individual exit
159    /// must never be interpreted as permission to retire its live peers.
160    Terminated {
161        /// True only for the committed process-wide exit.
162        group: bool,
163        /// Winner status from the backend lifecycle table, in wait(2) encoding.
164        wait_status: i32,
165    },
166    /// Successful exec replaced the selected callback's old image.
167    ImageReplaced,
168    /// Consuming logical task retirement cancelled the callback before entry.
169    Cancelled,
170    /// The run is terminal; its original error retains any partial signal effects.
171    Failed,
172}
173
174/// A consuming notification, not an ordinary scheduler resource request.
175#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
176pub struct SignalBoundaryReceipt {
177    /// Registered permit consumed by the backend.
178    pub permit: SignalDeliveryPermit,
179    /// Actual boundary outcome.
180    pub outcome: SignalBoundaryOutcome,
181}
182
183/// Shared run-owned facade. Implementations must not retain a Tool or Guest.
184///
185/// Calls are synchronous. Except for the explicitly named failure forwarding
186/// method, they must not call GlobalTool. No method may block on a guest
187/// callback, execute ordinary host IO, or drop retired descriptors while a
188/// signal/file-table guard is held. The caller supplies the causal scheduler
189/// fence; a snapshot by itself is not deterministic admission.
190pub trait ProcessSignalControl: Debug + Send + Sync {
191    /// Publish a complete SIGALRM/SI_KERNEL event to an exact process lifetime.
192    fn publish_alarm(
193        &self,
194        process: SignalProcessId,
195        event: SignalEvent,
196    ) -> ProcessSignalPublicationResult;
197
198    /// Publish one scheduler-authorized terminal child transition.
199    ///
200    /// The caller supplies the causal scheduler fence. A committed or
201    /// failed-after-commit result must never be retried; backends may return the
202    /// retained receipt idempotently if an exact duplicate nevertheless arrives.
203    /// This publication makes waitability visible but does not reap the backend
204    /// child status. A Tool that schedules a consuming wait must still execute
205    /// that wait through [`crate::Guest::inject`] before retiring Tool shadow
206    /// state; publication is not a substitute for the backend wait syscall.
207    ///
208    /// KVM may take its run-wide child-publication lock alone for an idempotent
209    /// duplicate preflight. Its committing path then acquires the exact parent's
210    /// process-signal transaction before the run-wide registry and signal-state
211    /// locks. A caller that holds a Tool scheduler mutex to make admission
212    /// atomic must preserve that nested order: Tool scheduler -> backend parent
213    /// transaction -> backend registry and signal state. No reverse path may
214    /// acquire the Tool mutex while retaining those backend locks.
215    /// An implementation used from that scheduler reservation must not call
216    /// back into Tool code or wait for the fenced parent wait or other guest
217    /// progress before returning.
218    fn publish_child_exit(&self, _completion: ChildExitCompletion) -> ChildExitPublicationResult {
219        ChildExitPublicationResult::RejectedBeforeCommit(Errno::ENOSYS)
220    }
221
222    /// Eligible live recipients for one pending signal, in ascending numeric
223    /// TID order. The caller intersects these with its causally admitted task
224    /// generations.
225    fn signal_recipients(
226        &self,
227        process: SignalProcessId,
228        signal: i32,
229    ) -> Result<Vec<SignalRecipient>, Errno>;
230
231    /// Eligible live recipients, in ascending numeric TID order. The caller
232    /// intersects these with its causally admitted task generations.
233    fn alarm_recipients(&self, process: SignalProcessId) -> Result<Vec<SignalRecipient>, Errno> {
234        self.signal_recipients(process, libc::SIGALRM)
235    }
236
237    /// Register one selected task. A second outstanding permit is not a retry.
238    fn reserve_delivery(&self, permit: SignalDeliveryPermit) -> Result<(), Errno>;
239
240    /// Settle a permit that did not remove a signal (for example, a masked
241    /// pending set after an authorized Tool operation). Exact duplicate receipts
242    /// are acknowledged, never interpreted as a second operation.
243    fn release_delivery(&self, permit: SignalDeliveryPermit) -> Result<(), Errno>;
244
245    /// Forward a retained publication failure to the run owner. This may call
246    /// GlobalTool, so the caller MUST release its scheduler mutex first.
247    fn finish_publication_failure(&self, process: SignalProcessId) -> Result<(), Errno>;
248
249    /// Forward one exact retained child-publication failure to the run owner.
250    ///
251    /// The receipt prevents a caller from acknowledging a different terminal
252    /// publication. This may call GlobalTool, so the caller MUST release its
253    /// scheduler mutex first.
254    fn finish_child_exit_publication_failure(
255        &self,
256        _receipt: ChildExitPublication,
257    ) -> Result<(), Errno> {
258        Err(Errno::ENOSYS)
259    }
260}
261
262/// The single run-level installation carries both publication and selection.
263#[derive(Clone, Debug)]
264pub struct BackendSignalControl {
265    /// Run-local weak backend facade.
266    pub process: Arc<dyn ProcessSignalControl>,
267}