Skip to main content

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}