seq_runtime/exit_code.rs
1//! Process exit code handling
2//!
3//! Stores the integer value returned from `main ( -- Int )` so the C-level
4//! `main` function can read it after the scheduler joins all strands and
5//! return it as the process exit code.
6//!
7//! # Lifetime
8//!
9//! - The user's `seq_main` function calls `patch_seq_set_exit_code` with the
10//! top-of-stack Int just before its stack is freed.
11//! - The C `main` function calls `patch_seq_get_exit_code` after
12//! `patch_seq_scheduler_run` returns and uses the value as the process
13//! exit code.
14//!
15//! # Concurrency
16//!
17//! The exit code is a single atomic global. Only the main strand writes to
18//! it, and only after all spawned strands have finished (since
19//! `scheduler_run` joins all strands). The C `main` reads it after
20//! `scheduler_run` returns. There is no race.
21//!
22//! Programs declaring `main ( -- )` (void main) never call the setter, so
23//! the exit code remains 0 — matching the historical behavior.
24
25use std::sync::atomic::{AtomicI64, Ordering};
26
27/// Process exit code, written by `seq_main` for `main ( -- Int )` programs.
28/// Defaults to 0 so void mains exit with success.
29static EXIT_CODE: AtomicI64 = AtomicI64::new(0);
30
31/// Set the process exit code.
32///
33/// Called by generated code at the end of `seq_main` when the user declared
34/// `main ( -- Int )`. The value is the top-of-stack Int.
35///
36/// `Release` ordering is sufficient: the write happens-before the C `main`
37/// reads the value with `Acquire`, after `scheduler_run` has joined all
38/// strands. There is no other reader.
39#[unsafe(no_mangle)]
40pub extern "C" fn patch_seq_set_exit_code(code: i64) {
41 EXIT_CODE.store(code, Ordering::Release);
42}
43
44/// Get the process exit code.
45///
46/// Called by the generated C `main` function after `scheduler_run` returns.
47/// Returns 0 if `patch_seq_set_exit_code` was never called (void main).
48///
49/// `Acquire` pairs with the `Release` store in `patch_seq_set_exit_code`.
50#[unsafe(no_mangle)]
51pub extern "C" fn patch_seq_get_exit_code() -> i64 {
52 EXIT_CODE.load(Ordering::Acquire)
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use serial_test::serial;
59
60 // Tests share a global AtomicI64, so they must run serially to avoid
61 // interleaving writes from one test with reads from another.
62
63 #[test]
64 #[serial]
65 fn test_default_is_zero() {
66 patch_seq_set_exit_code(0);
67 assert_eq!(patch_seq_get_exit_code(), 0);
68 }
69
70 #[test]
71 #[serial]
72 fn test_set_and_get() {
73 patch_seq_set_exit_code(42);
74 assert_eq!(patch_seq_get_exit_code(), 42);
75 patch_seq_set_exit_code(0);
76 }
77
78 #[test]
79 #[serial]
80 fn test_negative_exit_code() {
81 patch_seq_set_exit_code(-1);
82 assert_eq!(patch_seq_get_exit_code(), -1);
83 patch_seq_set_exit_code(0);
84 }
85}