borsa_core/stream.rs
1use tokio::task::JoinHandle;
2
3/// Abstraction over a handle that can be queried for completion and aborted.
4pub trait Abortable {
5 /// Abort the underlying task if it is still running.
6 fn abort(&mut self);
7 /// Return `true` if the underlying task has completed.
8 fn is_finished(&self) -> bool;
9}
10
11impl Abortable for JoinHandle<()> {
12 fn abort(&mut self) {
13 // JoinHandle::abort takes &self
14 Self::abort(self);
15 }
16
17 fn is_finished(&self) -> bool {
18 Self::is_finished(self)
19 }
20}
21
22/// Abstraction over a one-shot stop signal.
23pub trait Stoppable {
24 /// Send a best-effort stop signal to request graceful shutdown.
25 fn send(self);
26}
27
28impl Stoppable for tokio::sync::oneshot::Sender<()> {
29 fn send(self) {
30 let _ = Self::send(self, ());
31 }
32}
33
34/// Drop-time logic for stream handles:
35/// - send a best-effort stop signal if present
36/// - abort the task if it hasn't finished yet
37pub fn drop_impl<H, S>(inner: &mut Option<H>, stop_tx: &mut Option<S>)
38where
39 H: Abortable,
40 S: Stoppable,
41{
42 if let Some(tx) = stop_tx.take() {
43 tx.send();
44 }
45 if let Some(mut h) = inner.take()
46 && !h.is_finished()
47 {
48 h.abort();
49 }
50}
51
52/// Minimal stream handle abstraction for long-lived streaming tasks.
53///
54/// Lifecycle contract:
55/// - Prefer calling [`stop`](StreamHandle::stop) to request a graceful shutdown and await completion.
56/// - Call [`abort`](StreamHandle::abort) for immediate, non-graceful termination.
57/// - If dropped without an explicit shutdown, a best-effort stop signal is sent (if available) and
58/// the underlying task is then aborted. The task may not observe the stop signal before abort.
59#[derive(Debug)]
60pub struct StreamHandle {
61 inner: Option<tokio::task::JoinHandle<()>>,
62 stop_tx: Option<tokio::sync::oneshot::Sender<()>>,
63}
64
65impl StreamHandle {
66 /// Create a new `StreamHandle`.
67 ///
68 /// Parameters:
69 /// - `inner`: the spawned task driving the stream.
70 /// - `stop_tx`: a one-shot used to request a graceful stop.
71 ///
72 /// Returns a handle that can be used to stop or abort the stream.
73 #[must_use]
74 pub const fn new(
75 inner: tokio::task::JoinHandle<()>,
76 stop_tx: tokio::sync::oneshot::Sender<()>,
77 ) -> Self {
78 Self {
79 inner: Some(inner),
80 stop_tx: Some(stop_tx),
81 }
82 }
83
84 /// Create a `StreamHandle` that can only abort the task (no graceful stop).
85 ///
86 /// This constructor is intended for connectors that do not support a
87 /// cooperative shutdown signal. Dropping the handle (or calling
88 /// [`abort`](Self::abort)) will force-cancel the underlying task.
89 #[must_use]
90 pub const fn new_abort_only(inner: tokio::task::JoinHandle<()>) -> Self {
91 Self {
92 inner: Some(inner),
93 stop_tx: None,
94 }
95 }
96
97 /// Gracefully stop the underlying stream task and await its completion.
98 ///
99 /// Sends a stop signal if available, then awaits the task. Any errors
100 /// from the task are ignored.
101 pub async fn stop(mut self) {
102 if let Some(tx) = self.stop_tx.take() {
103 let _ = tx.send(());
104 }
105 if let Some(inner) = self.inner.take() {
106 let _ = inner.await;
107 }
108 }
109
110 /// Force-abort the underlying stream task without waiting for completion.
111 ///
112 /// Prefer [`stop`](Self::stop) when possible to allow cleanup.
113 pub fn abort(mut self) {
114 if let Some(inner) = self.inner.take() {
115 inner.abort();
116 }
117 }
118}
119
120impl Drop for StreamHandle {
121 fn drop(&mut self) {
122 crate::stream::drop_impl(&mut self.inner, &mut self.stop_tx);
123 }
124}