Skip to main content

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(&REGISTRY);
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(&REGISTRY);
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(&REGISTRY);
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;