Skip to main content

laminar_connectors/connector/
task_tracking.rs

1//! Detached connector-task admission and terminal lifetime tracking.
2
3use std::sync::atomic::{AtomicUsize, Ordering};
4use std::sync::{Arc, Weak};
5
6use tokio::sync::Notify;
7
8/// What the runtime must do when a started connector operation is cancelled.
9///
10/// This is an internal connector/driver capability, not a deployment option.
11/// Cancellation always respects the runtime-owned deadline. A connector may be
12/// reused only when dropping the exact future is known to preserve its state;
13/// otherwise the runtime retires the complete connector generation.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
15#[serde(rename_all = "snake_case")]
16pub enum ConnectorCancellationPolicy {
17    /// Dropping an in-flight future leaves the connector valid for recovery or reuse.
18    CancelSafe,
19    /// Dropping an in-flight future may leave its external outcome unknown, so
20    /// the connector instance must not process another operation.
21    RetireConnector,
22}
23
24const CONNECTOR_TASK_OWNER_DROPPED: usize = 1usize << (usize::BITS - 1);
25
26pub(super) struct ConnectorTaskState {
27    state: AtomicUsize,
28    terminated: Notify,
29}
30
31/// Sole admission authority for detached tasks owned by one connector generation.
32///
33/// The owner must live inside the connector. Dropping it seals the generation so
34/// terminal completion can be observed after every admitted task guard is gone.
35pub struct ConnectorTaskOwner {
36    inner: Arc<ConnectorTaskState>,
37}
38
39/// Cloneable, non-owning admission handle for dynamically spawned connector tasks.
40///
41/// The handle does not keep the task generation open. Admission fails after
42/// the sole [`ConnectorTaskOwner`] is dropped, including when existing task
43/// guards still keep the generation observable.
44#[derive(Clone)]
45pub struct ConnectorTaskAdmission {
46    pub(super) inner: Weak<ConnectorTaskState>,
47}
48
49/// Cloneable observer for terminal completion of one connector generation.
50#[derive(Clone)]
51pub struct ConnectorTaskTracker {
52    inner: Arc<ConnectorTaskState>,
53}
54
55/// RAII proof that one connector-owned task is still active.
56///
57/// Move the guard into the task before spawning it and retain it for the task's
58/// full lifetime.
59#[must_use = "dropping the guard marks its connector task complete"]
60pub struct ConnectorTaskGuard {
61    inner: Arc<ConnectorTaskState>,
62}
63
64impl ConnectorTaskOwner {
65    /// Create the sole task owner and its cloneable terminal observer.
66    #[must_use]
67    pub fn new() -> (Self, ConnectorTaskTracker) {
68        let inner = Arc::new(ConnectorTaskState {
69            state: AtomicUsize::new(0),
70            terminated: Notify::new(),
71        });
72        (
73            Self {
74                inner: Arc::clone(&inner),
75            },
76            ConnectorTaskTracker { inner },
77        )
78    }
79
80    /// Create a non-owning admission handle for tasks discovered by owned work.
81    ///
82    /// This is intended for accept loops and similar dynamic task producers.
83    /// Cloning the handle never extends the connector generation's admission
84    /// lifetime.
85    #[must_use]
86    pub fn admission(&self) -> ConnectorTaskAdmission {
87        ConnectorTaskAdmission {
88            inner: Arc::downgrade(&self.inner),
89        }
90    }
91
92    /// Admit one task into this live connector generation.
93    ///
94    /// The returned guard must be created before the task is spawned and moved
95    /// into that task. `None` means the generation can no longer admit work.
96    #[must_use]
97    pub fn track(&self) -> Option<ConnectorTaskGuard> {
98        track_connector_task(&self.inner)
99    }
100}
101
102impl ConnectorTaskAdmission {
103    /// Admit one dynamic task while the connector generation remains open.
104    ///
105    /// Returns `None` once the sole owner has been dropped. A successful
106    /// admission remains tracked until its returned guard is dropped.
107    #[must_use]
108    pub fn track(&self) -> Option<ConnectorTaskGuard> {
109        let inner = self.inner.upgrade()?;
110        track_connector_task(&inner)
111    }
112}
113
114fn track_connector_task(inner: &Arc<ConnectorTaskState>) -> Option<ConnectorTaskGuard> {
115    let mut observed = inner.state.load(Ordering::Acquire);
116    loop {
117        if observed & CONNECTOR_TASK_OWNER_DROPPED != 0 {
118            return None;
119        }
120        let next = observed.checked_add(1)?;
121        if next & CONNECTOR_TASK_OWNER_DROPPED != 0 {
122            return None;
123        }
124        match inner
125            .state
126            .compare_exchange_weak(observed, next, Ordering::AcqRel, Ordering::Acquire)
127        {
128            Ok(_) => {
129                return Some(ConnectorTaskGuard {
130                    inner: Arc::clone(inner),
131                });
132            }
133            Err(actual) => observed = actual,
134        }
135    }
136}
137
138impl Drop for ConnectorTaskOwner {
139    fn drop(&mut self) {
140        let previous = self
141            .inner
142            .state
143            .fetch_or(CONNECTOR_TASK_OWNER_DROPPED, Ordering::AcqRel);
144        debug_assert_eq!(previous & CONNECTOR_TASK_OWNER_DROPPED, 0);
145        if previous == 0 {
146            self.inner.terminated.notify_waiters();
147        }
148    }
149}
150
151impl ConnectorTaskTracker {
152    /// Whether the owner and all task guards have been dropped.
153    #[must_use]
154    pub fn is_terminated(&self) -> bool {
155        self.inner.state.load(Ordering::Acquire) == CONNECTOR_TASK_OWNER_DROPPED
156    }
157
158    /// Wait until the owner and all task guards have been dropped.
159    pub async fn wait_terminated(&self) {
160        loop {
161            let notified = self.inner.terminated.notified();
162            tokio::pin!(notified);
163            notified.as_mut().enable();
164            if self.is_terminated() {
165                return;
166            }
167            notified.await;
168        }
169    }
170}
171
172impl Drop for ConnectorTaskGuard {
173    fn drop(&mut self) {
174        let previous = self.inner.state.fetch_sub(1, Ordering::AcqRel);
175        debug_assert_ne!(previous & !CONNECTOR_TASK_OWNER_DROPPED, 0);
176        if previous == CONNECTOR_TASK_OWNER_DROPPED | 1 {
177            self.inner.terminated.notify_waiters();
178        }
179    }
180}