pub struct FlowHandle { /* private fields */ }Expand description
A reference to a spawned flow, returned by
super::runtime::Runtime::spawn.
Holding a FlowHandle does not keep the flow alive — it is a way to
(a) read its FlowId for addressing with Send, and (b) block the
calling native thread until it finishes via FlowHandle::join.
Dropping without joining is fine (fire-and-forget).
Implementations§
Source§impl FlowHandle
impl FlowHandle
pub fn id(&self) -> FlowId
Sourcepub fn join(self) -> FlowOutcome
pub fn join(self) -> FlowOutcome
Block the current (native) thread until the flow terminates. Never call from inside a worker / from bytecode.
Returns FlowOutcome::Failed rather than blocking forever if the
flow was destroyed without producing an outcome — most commonly
Runtime::shutdown while the flow was suspended, since shutdown does
not drain flows out of the timer or the worker deques. The message
comes from super::error::RuntimeError::Abandoned, so it is
distinguishable from a flow that genuinely faulted.
Examples found in repository?
9fn main() {
10 let chunk = samples::ping_pong();
11 let rt = match Runtime::with_natives_and_config(
12 chunk,
13 std_native_table(),
14 RuntimeConfig {
15 workers: 1,
16 quantum: 10_000,
17 mailbox: byteflow::MailboxConfig::DEFAULT,
18 },
19 ) {
20 Ok(rt) => rt,
21 Err(e) => {
22 eprintln!("runtime: {e}");
23 std::process::exit(1);
24 }
25 };
26 let Some(main) = rt.function_index("main") else {
27 eprintln!("missing main");
28 std::process::exit(1);
29 };
30 let handle = match rt.spawn(main, &[]) {
31 Ok(h) => h,
32 Err(e) => {
33 eprintln!("spawn: {e}");
34 std::process::exit(1);
35 }
36 };
37 let outcome = handle.join();
38 let metrics = rt.metrics();
39 rt.shutdown();
40
41 match outcome {
42 FlowOutcome::Completed(Value::Int(2)) => {
43 println!("pong replied 2 (Atomic Hop)");
44 println!("{metrics}");
45 }
46 other => {
47 eprintln!("unexpected {other:?}");
48 std::process::exit(1);
49 }
50 }
51}More examples
21fn main() {
22 let chunk = samples::atomic_request_reply();
23 let rt = match Runtime::with_natives_and_config(
24 chunk,
25 std_native_table(),
26 RuntimeConfig {
27 workers: 2,
28 quantum: 10_000,
29 mailbox: byteflow::MailboxConfig::DEFAULT,
30 },
31 ) {
32 Ok(rt) => rt,
33 Err(e) => {
34 eprintln!("runtime: {e}");
35 std::process::exit(1);
36 }
37 };
38 let Some(main) = rt.function_index("main") else {
39 eprintln!("missing main");
40 std::process::exit(1);
41 };
42 let handle = match rt.spawn(main, &[]) {
43 Ok(h) => h,
44 Err(e) => {
45 eprintln!("spawn: {e}");
46 std::process::exit(1);
47 }
48 };
49 let outcome = handle.join();
50 let metrics = rt.metrics();
51 rt.shutdown();
52
53 match outcome {
54 FlowOutcome::Completed(Value::Int(42)) => {
55 println!("atomic request-reply ok: payload=42");
56 println!("{metrics}");
57 }
58 other => {
59 eprintln!("unexpected {other:?}");
60 std::process::exit(1);
61 }
62 }
63}19fn main() {
20 let n: u32 = match std::env::args().nth(1) {
21 Some(s) => match s.parse() {
22 Ok(v) => v,
23 Err(_) => 50_000,
24 },
25 None => 50_000,
26 };
27
28 let workers = match std::thread::available_parallelism() {
29 Ok(p) => p.get(),
30 Err(_) => 1,
31 };
32
33 let rt = match Runtime::with_config(
34 trivial_chunk(),
35 RuntimeConfig {
36 workers,
37 quantum: 10_000,
38 mailbox: byteflow::MailboxConfig::DEFAULT,
39 },
40 ) {
41 Ok(rt) => rt,
42 Err(e) => {
43 eprintln!("runtime: {e}");
44 std::process::exit(1);
45 }
46 };
47 let Some(worker_fn) = rt.function_index("worker") else {
48 eprintln!("missing worker");
49 std::process::exit(1);
50 };
51
52 let start = Instant::now();
53 let mut handles = Vec::with_capacity(n as usize);
54 for _ in 0..n {
55 match rt.spawn(worker_fn, &[]) {
56 Ok(h) => handles.push(h),
57 Err(e) => {
58 eprintln!("spawn: {e}");
59 std::process::exit(1);
60 }
61 }
62 }
63 let mut ok = 0u32;
64 for h in handles {
65 if matches!(h.join(), FlowOutcome::Completed(Value::Int(1))) {
66 ok += 1;
67 }
68 }
69 let elapsed = start.elapsed();
70 let metrics = rt.metrics();
71 rt.shutdown();
72
73 let secs = elapsed.as_secs_f64().max(1e-9);
74 println!("processes={ok}/{n}");
75 println!("workers={workers}");
76 println!("elapsed_ms={:.2}", elapsed.as_secs_f64() * 1000.0);
77 println!("spawns_per_sec={:.0}", ok as f64 / secs);
78 println!("{metrics}");
79}Sourcepub fn try_join(&self) -> Option<FlowOutcome>
pub fn try_join(&self) -> Option<FlowOutcome>
The outcome if the flow has already terminated, None if it is still
running. Never waits.
For embedders that drive their own loop and cannot surrender a thread
to FlowHandle::join. Takes &self, so it can be polled until it
answers; note that the outcome is handed out exactly once, and a
further poll after that reports
super::error::RuntimeError::AlreadyCollected as
FlowOutcome::Failed rather than repeating it.
Sourcepub fn join_timeout(&self, timeout: Duration) -> Option<FlowOutcome>
pub fn join_timeout(&self, timeout: Duration) -> Option<FlowOutcome>
Wait up to timeout for the flow to terminate; None if it is still
running when the bound elapses.
This is the variant to reach for in anything with a deadline — a control loop, a watchdog, a test harness — since it is the only join whose worst-case duration the caller chooses.
Sourcepub fn join_deadline(&self, deadline: Instant) -> Option<FlowOutcome>
pub fn join_deadline(&self, deadline: Instant) -> Option<FlowOutcome>
FlowHandle::join_timeout against an absolute deadline, for callers
that already track one and must not have it drift across repeated
waits.