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}