windows_ioring_sys/sys/registered.rs
1// Copyright (c) Mike Grier
2//! Registering a [`Responses`] for one ring (M35.1, [D-85](../../DESIGN-NOTES.md#d-85)).
3//!
4//! # One ring, any thread
5//!
6//! A responder registered here answers the seam calls of the one ring it was
7//! registered for, on whatever thread makes them. That is what a ring used
8//! from more than one thread needs -- a pool thread draining it, a consumer
9//! submitting to it -- and what the thread-local form it replaced (`M35.2`)
10//! could not give: there, a call from a thread with nothing installed reached
11//! the kernel silently, so an injected failure could be undone by a real call
12//! on another thread and a test pass for the wrong reason.
13//!
14//! Tests still cannot answer each other's calls, which is what made the
15//! thread-local form thread-local: two tests do not share a ring. The key is
16//! the ring's `RingId`, which is process-unique and never reused, where a raw
17//! handle value can be reused once `CloseIoRing` has run.
18//!
19//! # How long a registration lasts
20//!
21//! **Until its ring drops**, after the ring's rundown and before
22//! `CloseIoRing` -- so a responder holding operations the kernel never saw
23//! still answers the rundown that pops them -- or until
24//! [`Registration::end`] ends it earlier. **Dropping the [`Registration`]
25//! ends nothing.** That is deliberate: locals drop in reverse declaration
26//! order, so a registration ended by its handle's drop would, in the ordinary
27//! `let ring = ..; let registration = register(&ring, ..);` shape, end before
28//! the ring's rundown ran -- the very ordering hazard this form exists to
29//! remove.
30//!
31//! # Re-entrancy and concurrency
32//!
33//! - A responder is not consulted re-entrantly for its own ring. A call it
34//! makes into the seam for that ring finds it already answering on this
35//! thread and falls through to the kernel.
36//! - Calls on one ring from two threads are serialized on the responder's
37//! lock. A ring is not `Sync`, so they do not overlap in practice; a call
38//! that did would wait rather than fall through to the kernel, which would
39//! be wrong silently.
40
41use std::cell::RefCell;
42use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
43
44use super::Responses;
45use crate::IoRing;
46use crate::accounting::RingId;
47
48/// A registered responder, behind its own lock so one ring's call never waits
49/// on another ring's. `None` once the registration has ended, for a call that
50/// cloned it just before.
51type Slot = Arc<Mutex<Option<Box<dyn Responses + Send>>>>;
52
53/// Every live registration. A `Vec` rather than a map: it holds a handful of
54/// entries at most, and `Mutex::new(Vec::new())` is `const`.
55static REGISTRY: Mutex<Vec<(RingId, Slot)>> = Mutex::new(Vec::new());
56
57thread_local! {
58 /// The rings whose responders this thread is answering for right now, so
59 /// a re-entrant call is recognised rather than deadlocking on the slot.
60 static ANSWERING: RefCell<Vec<RingId>> = const { RefCell::new(Vec::new()) };
61}
62
63/// Lock `mutex`. A responder that panicked while answering leaves its own
64/// state as the panic found it; the registry itself is only ever pushed to and
65/// removed from, so neither is left inconsistent.
66fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
67 mutex.lock().unwrap_or_else(PoisonError::into_inner)
68}
69
70/// Marks `ring` as being answered on this thread until dropped.
71struct Answering(RingId);
72
73impl Answering {
74 /// `None` if this thread is already answering for `ring`, or its
75 /// thread-locals are gone.
76 fn enter(ring: RingId) -> Option<Self> {
77 ANSWERING
78 .try_with(|answering| {
79 let mut answering = answering.try_borrow_mut().ok()?;
80 if answering.contains(&ring) {
81 return None;
82 }
83 answering.push(ring);
84 Some(Self(ring))
85 })
86 .ok()
87 .flatten()
88 }
89}
90
91impl Drop for Answering {
92 fn drop(&mut self) {
93 let _ = ANSWERING.try_with(|answering| {
94 if let Ok(mut answering) = answering.try_borrow_mut()
95 && let Some(at) = answering.iter().rposition(|ring| *ring == self.0)
96 {
97 answering.remove(at);
98 }
99 });
100 }
101}
102
103/// Run `f` against `ring`'s responder, if it has one and this thread is not
104/// already answering for it. `None` is the seam's signal to make the real
105/// call.
106pub(crate) fn with<R>(ring: RingId, f: impl FnOnce(&mut dyn Responses) -> R) -> Option<R> {
107 let slot = {
108 let registry = lock(®ISTRY);
109 let (_, slot) = registry.iter().find(|(id, _)| *id == ring)?;
110 Arc::clone(slot)
111 };
112 let _answering = Answering::enter(ring)?;
113 let mut responder = lock(&slot);
114 let responder = responder.as_mut()?;
115 Some(f(&mut **responder))
116}
117
118/// Register `responder` for the ring identified by `ring`.
119///
120/// # Panics
121///
122/// If that ring already has one: which of two answered a call would depend on
123/// registration order.
124pub(crate) fn register_id(ring: RingId, responder: Box<dyn Responses + Send>) -> Registration {
125 let mut registry = lock(®ISTRY);
126 assert!(
127 !registry.iter().any(|(id, _)| *id == ring),
128 "a kernel responder is already registered for this ring"
129 );
130 registry.push((ring, Arc::new(Mutex::new(Some(responder)))));
131 Registration { ring }
132}
133
134/// End `ring`'s registration, if it has one, handing its responder back.
135///
136/// The responder is taken under its own lock, so a call answering for the ring
137/// on another thread finishes first, and a call that had already found the
138/// slot finds it empty and falls through.
139fn end_id(ring: RingId) -> Option<Box<dyn Responses + Send>> {
140 let slot = {
141 let mut registry = lock(®ISTRY);
142 let at = registry.iter().position(|(id, _)| *id == ring)?;
143 registry.swap_remove(at).1
144 };
145 lock(&slot).take()
146}
147
148/// `ring` is dropping, and its rundown has run: end its registration, if it
149/// has one. Called by `IoRing`'s `Drop` before `CloseIoRing`.
150pub(crate) fn ring_dropped(ring: RingId) {
151 // Dropped here, outside every lock.
152 drop(end_id(ring));
153}
154
155/// Register `responder` to answer `ring`'s seam calls, on whatever thread
156/// makes them, until the ring drops or [`Registration::end`] is called.
157///
158/// See the module documentation for why dropping the returned
159/// [`Registration`] does **not** end it.
160///
161/// # Panics
162///
163/// If `ring` already has a responder registered.
164pub fn register<T, X, K>(
165 ring: &IoRing<T, X, K>,
166 responder: Box<dyn Responses + Send>,
167) -> Registration {
168 register_id(ring.ring_id(), responder)
169}
170
171/// A responder's registration for one ring (D-85).
172///
173/// Ends when its ring drops, or at [`Registration::end`]. Dropping this value
174/// ends nothing; see the module documentation for why.
175#[derive(Debug)]
176pub struct Registration {
177 ring: RingId,
178}
179
180impl Registration {
181 /// End the registration now, before its ring drops, handing the responder
182 /// back. Once it returns, the ring's calls reach the kernel.
183 ///
184 /// `None` if the registration had already ended because its ring dropped.
185 pub fn end(self) -> Option<Box<dyn Responses + Send>> {
186 end_id(self.ring)
187 }
188}
189
190#[cfg(test)]
191mod tests;