Skip to main content

with_executor

Function with_executor 

Source
pub fn with_executor<R>(ex: &Rc<RefCell<Executor>>, f: impl FnOnce() -> R) -> R
Expand description

Run f with ex set as the current executor.

Signal callbacks and spawn_global calls inside f will be routed to ex instead of the global thread-local executor. Restores the previous executor afterward.

§Signal routing constraints

Auralis uses a single global schedule hook (installed once by the first call to init_flush_scheduler) that decides where signal notifications land by checking the current executor at the time the notification fires, not at the time Signal::set is called.

This design implies two hard requirements for multi-instance users:

  1. init_flush_scheduler must be called at least once — without it, Signal::set falls back to synchronous callback execution, which breaks the deferred-notification model and can cause re-entrant borrow panics.
  2. The instance executor must still be “current” when the flush runs — if with_executor has already exited, deferred callbacks from signals set inside f will be routed to the global executor (or synchronously if no global hook is installed).

For the typical single-threaded case (Wasm, game loop, CLI), both requirements are satisfied trivially: call init_flush_scheduler once at startup and never use with_executor. For SSR / multi-tenant servers, ensure that with_executor wraps the entire request lifecycle — from signal creation through the final flush.

§Example

use auralis_task::Executor;

let ex = Executor::new_instance();
Executor::install_flush_scheduler(&ex, my_scheduler);
auralis_task::with_executor(&ex, || {
    // Signal notifications and task spawns here go to `ex`.
});
Examples found in repository?
examples/multi_instance_isolated.rs (lines 53-57)
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}