Skip to main content

byteflow/scheduler/
handle.rs

1use std::time::{Duration, Instant};
2
3use super::oneshot::{self, JoinState};
4use super::process::{FlowId, FlowOutcome};
5
6/// A reference to a spawned **flow**, returned by
7/// [`super::runtime::Runtime::spawn`].
8///
9/// Holding a `FlowHandle` does not keep the flow alive — it is a way to
10/// (a) read its [`FlowId`] for addressing with `Send`, and (b) block the
11/// *calling native thread* until it finishes via [`FlowHandle::join`].
12/// Dropping without joining is fine (fire-and-forget).
13pub struct FlowHandle {
14    pub(crate) id: FlowId,
15    pub(crate) receiver: oneshot::Receiver<FlowOutcome>,
16}
17
18impl FlowHandle {
19    pub fn id(&self) -> FlowId {
20        self.id
21    }
22
23    /// Block the current (native) thread until the flow terminates.
24    /// **Never call from inside a worker / from bytecode.**
25    ///
26    /// Returns [`FlowOutcome::Failed`] rather than blocking forever if the
27    /// flow was destroyed without producing an outcome — most commonly
28    /// `Runtime::shutdown` while the flow was suspended, since shutdown does
29    /// not drain flows out of the timer or the worker deques. The message
30    /// comes from [`super::error::RuntimeError::Abandoned`], so it is
31    /// distinguishable from a flow that genuinely faulted.
32    pub fn join(self) -> FlowOutcome {
33        match self.receiver.join() {
34            Ok(outcome) => outcome,
35            Err(e) => FlowOutcome::Failed(e.to_string()),
36        }
37    }
38
39    /// The outcome if the flow has already terminated, `None` if it is still
40    /// running. Never waits.
41    ///
42    /// For embedders that drive their own loop and cannot surrender a thread
43    /// to [`FlowHandle::join`]. Takes `&self`, so it can be polled until it
44    /// answers; note that the outcome is handed out exactly once, and a
45    /// further poll after that reports
46    /// [`super::error::RuntimeError::AlreadyCollected`] as
47    /// [`FlowOutcome::Failed`] rather than repeating it.
48    pub fn try_join(&self) -> Option<FlowOutcome> {
49        Self::settle(self.receiver.try_join())
50    }
51
52    /// Wait up to `timeout` for the flow to terminate; `None` if it is still
53    /// running when the bound elapses.
54    ///
55    /// This is the variant to reach for in anything with a deadline — a
56    /// control loop, a watchdog, a test harness — since it is the only join
57    /// whose worst-case duration the caller chooses.
58    pub fn join_timeout(&self, timeout: Duration) -> Option<FlowOutcome> {
59        Self::settle(self.receiver.join_timeout(timeout))
60    }
61
62    /// [`FlowHandle::join_timeout`] against an absolute deadline, for callers
63    /// that already track one and must not have it drift across repeated
64    /// waits.
65    pub fn join_deadline(&self, deadline: Instant) -> Option<FlowOutcome> {
66        Self::settle(self.receiver.join_deadline(deadline))
67    }
68
69    /// Collapse a bounded-wait result into the handle's public vocabulary:
70    /// `None` for "still running", `Some(Failed)` for an infrastructure
71    /// error, since an embedder polling a handle has no separate error
72    /// channel to report one on.
73    fn settle(state: Result<JoinState<FlowOutcome>, super::error::RuntimeError>) -> Option<FlowOutcome> {
74        match state {
75            Ok(JoinState::Ready(outcome)) => Some(outcome),
76            Ok(JoinState::Pending) => None,
77            Err(e) => Some(FlowOutcome::Failed(e.to_string())),
78        }
79    }
80}