Skip to main content

multi_instance_isolated/
multi_instance_isolated.rs

1//! Multi-instance executor isolation: each "request" gets its own
2//! `Executor + TaskScope + Signal`, with zero cross-request leakage.
3//!
4//! ```bash
5//! cargo run --example multi_instance_isolated -p auralis-task
6//! ```
7
8use std::cell::{Cell, RefCell};
9use std::rc::Rc;
10
11use auralis_signal::Signal;
12use auralis_task::{Executor, ScheduleFlush, TaskScope};
13
14// -- synchronous flush scheduler --------------------------------------------
15struct SyncScheduler;
16impl ScheduleFlush for SyncScheduler {
17    fn schedule(&self, callback: Box<dyn FnOnce()>) {
18        callback();
19    }
20}
21
22// -- simulated "request handler" --------------------------------------------
23//
24// Each request creates its own Executor + Scope + Signals.  The key
25// guarantee: nothing leaks between requests — drop the scope and
26// everything is cleaned up.
27
28fn handle_request(request_id: u32) -> String {
29    let ex = Executor::new_instance();
30    Executor::install_flush_scheduler(&ex, Rc::new(SyncScheduler));
31
32    let scope = TaskScope::with_executor(&ex);
33    let data = Signal::new(String::new());
34
35    // "route handler" spawns a reactive effect
36    let d = data.clone();
37    scope.spawn(async move {
38        loop {
39            d.changed().await;
40            let val = d.read();
41            if val == "done" {
42                break;
43            }
44        }
45    });
46
47    // "middleware" sets up cleanup
48    let cleanup_called = Rc::new(Cell::new(false));
49    let cc = Rc::clone(&cleanup_called);
50    scope.on_cleanup(move || cc.set(true));
51
52    // "business logic" — would be I/O in real app
53    let result = auralis_task::with_executor(&ex, || {
54        data.set(format!("request {request_id}: processing"));
55        data.set(format!("request {request_id}: done"));
56        data.read()
57    });
58
59    // Drop scope → cancels effect, runs cleanup.
60    drop(scope);
61
62    assert!(cleanup_called.get());
63    result
64}
65
66fn main() {
67    // ---- Pattern 1: sequential requests (same thread, isolated) ----------
68    println!("=== 1. Sequential request isolation ===");
69    let r1 = handle_request(1);
70    let r2 = handle_request(2);
71    println!("  {r1}");
72    println!("  {r2}");
73    assert_eq!(r1, "request 1: done");
74    assert_eq!(r2, "request 2: done");
75
76    // ---- Pattern 2: same-signal isolation test (different executors) -----
77    println!("\n=== 2. Cross-executor signal isolation ===");
78    {
79        let ex_a = Executor::new_instance();
80        Executor::install_flush_scheduler(&ex_a, Rc::new(SyncScheduler));
81        let ex_b = Executor::new_instance();
82        Executor::install_flush_scheduler(&ex_b, Rc::new(SyncScheduler));
83
84        let sig_a1 = Signal::new(0i32);
85        let sig_b1 = Signal::new(0i32);
86
87        // Scope on executor A sets sig_a and reads sig_b.
88        let scope_a = TaskScope::with_executor(&ex_a);
89        let a_val = Rc::new(RefCell::new(Vec::new()));
90        let av = Rc::clone(&a_val);
91        {
92            let s = sig_a1.clone();
93            scope_a.spawn(async move {
94                s.set(42);
95                av.borrow_mut().push(s.read());
96            });
97        }
98        drop(scope_a);
99
100        // Scope on executor B sets sig_b and reads sig_a.
101        let scope_b = TaskScope::with_executor(&ex_b);
102        let b_val = Rc::new(RefCell::new(Vec::new()));
103        let bv = Rc::clone(&b_val);
104        {
105            let s = sig_b1.clone();
106            scope_b.spawn(async move {
107                s.set(99);
108                bv.borrow_mut().push(s.read());
109            });
110        }
111        drop(scope_b);
112
113        // Each executor only sees its own signal changes.
114        assert_eq!(*a_val.borrow(), vec![42]);
115        assert_eq!(*b_val.borrow(), vec![99]);
116        assert_eq!(sig_a1.read(), 42);
117        assert_eq!(sig_b1.read(), 99);
118    }
119
120    // ---- Pattern 3: the SSR mental model (one request = one executor) ----
121    println!("\n=== 3. SSR mental model ===");
122    for i in 0..3 {
123        let ex = Executor::new_instance();
124        Executor::install_flush_scheduler(&ex, Rc::new(SyncScheduler));
125        let counter = Signal::new(0i32);
126
127        let scope = TaskScope::with_executor(&ex);
128        let c = counter.clone();
129        let results = Rc::new(RefCell::new(Vec::new()));
130        let res = Rc::clone(&results);
131
132        scope.spawn(async move {
133            loop {
134                c.changed().await;
135                let v = c.read();
136                res.borrow_mut().push(v);
137                if v >= 3 {
138                    break;
139                }
140            }
141        });
142
143        auralis_task::with_executor(&ex, || {
144            counter.set(1);
145            counter.set(2);
146            counter.set(3);
147        });
148
149        drop(scope);
150        assert_eq!(*results.borrow(), vec![1, 2, 3]);
151        println!("  request {i}: signals {:?}", results.borrow());
152    }
153
154    println!("\nAll patterns completed — zero cross-request leakage.");
155}