seq_runtime/weave/yield_op.rs
1//! Weave-side yield: `patch_seq_yield` sends a value back through the weave
2//! context and awaits the next resume value.
3
4use crate::stack::{Stack, pop, push};
5use crate::value::{Value, WeaveMessage};
6use std::sync::Arc;
7
8use super::strand_lifecycle::{block_forever, cleanup_strand};
9
10/// Yield a value from within a woven strand
11///
12/// Stack effect: ( WeaveCtx a -- WeaveCtx a )
13///
14/// Sends value `a` to the caller and waits for the next resume value.
15/// The WeaveCtx must be passed through - it contains the channels.
16///
17/// # Error Handling
18///
19/// This function never panics (panicking in extern "C" is UB). On error:
20/// - Type mismatch: eprintln + cleanup + block forever
21/// - Channel closed: cleanup + block forever
22///
23/// The coroutine is marked as completed before blocking, so the program
24/// can still terminate normally.
25///
26/// # Safety
27/// Stack must have a value on top and WeaveCtx below it
28#[unsafe(no_mangle)]
29pub unsafe extern "C" fn patch_seq_yield(stack: Stack) -> Stack {
30 // Note: We can't use assert! here (it panics). A null stack is a fatal
31 // programming error, but we handle it gracefully to avoid UB.
32 if stack.is_null() {
33 eprintln!("yield: stack is null (fatal programming error)");
34 crate::arena::arena_reset();
35 cleanup_strand();
36 block_forever();
37 }
38
39 // Pop the value to yield
40 let (stack, value) = unsafe { pop(stack) };
41
42 // Pop the WeaveCtx
43 let (stack, ctx) = unsafe { pop(stack) };
44
45 let (yield_chan, resume_chan) = match &ctx {
46 Value::WeaveCtx {
47 yield_chan,
48 resume_chan,
49 } => (Arc::clone(yield_chan), Arc::clone(resume_chan)),
50 _ => {
51 // Type mismatch - yield called without WeaveCtx on stack
52 // This is a programming error but we can't panic (UB)
53 eprintln!(
54 "yield: expected WeaveCtx on stack, got {:?}. \
55 yield can only be called inside strand.weave with context threaded through.",
56 ctx
57 );
58 crate::arena::arena_reset();
59 cleanup_strand();
60 block_forever();
61 }
62 };
63
64 // Wrap value in WeaveMessage for sending
65 let msg_to_send = WeaveMessage::Value(value.clone());
66
67 // Send the yielded value
68 if yield_chan.sender.send(msg_to_send).is_err() {
69 // Channel unexpectedly closed - caller dropped the handle
70 // Clean up and block forever (can't panic in extern "C")
71 // We're still active here, so call cleanup_strand
72 crate::arena::arena_reset();
73 cleanup_strand();
74 block_forever();
75 }
76
77 // IMPORTANT: Become "dormant" before waiting for resume (fixes #287)
78 // This allows the scheduler to exit if the program ends while we're waiting.
79 // We'll re-activate after receiving the resume value.
80 use crate::scheduler::ACTIVE_STRANDS;
81 use std::sync::atomic::Ordering;
82 ACTIVE_STRANDS.fetch_sub(1, Ordering::AcqRel);
83
84 // Wait for resume value (we're dormant now - not counted as active)
85 let resume_msg = match resume_chan.receiver.recv() {
86 Ok(msg) => msg,
87 Err(_) => {
88 // Resume channel closed - caller dropped the handle
89 // We're already dormant (decremented above), don't call cleanup_strand
90 crate::arena::arena_reset();
91 block_forever();
92 }
93 };
94
95 // Handle the message
96 match resume_msg {
97 WeaveMessage::Cancel => {
98 // Weave was cancelled - signal completion and exit cleanly
99 // We're already dormant (decremented above), don't call cleanup_strand
100 let _ = yield_chan.sender.send(WeaveMessage::Done);
101 crate::arena::arena_reset();
102 block_forever();
103 }
104 WeaveMessage::Value(resume_value) => {
105 // Re-activate: we're about to run user code again
106 // Use AcqRel for consistency with the decrement above
107 ACTIVE_STRANDS.fetch_add(1, Ordering::AcqRel);
108
109 // Push WeaveCtx back, then resume value
110 let stack = unsafe { push(stack, ctx) };
111 unsafe { push(stack, resume_value) }
112 }
113 WeaveMessage::Done => {
114 // Protocol error - Done should only be sent on yield_chan
115 // We're already dormant (decremented above), don't call cleanup_strand
116 crate::arena::arena_reset();
117 block_forever();
118 }
119 }
120}