Skip to main content

cranpose_core/
concurrency.rs

1//! Composition-scoped structured concurrency.
2//!
3//! `LaunchedEffect` covers work that starts because a key changed. This covers
4//! the rest: work started from an event handler, timed work, work that feeds a
5//! piece of state, and blocking work that must not run on the UI thread.
6//! Everything here is owned by the composition — a scope cancels its tasks when
7//! it leaves, and a timer stops when nothing is waiting on it — so an
8//! application never keeps its own task list or its own "is this still alive"
9//! flag.
10
11#[cfg(not(target_arch = "wasm32"))]
12use std::sync::{Condvar, Mutex};
13use std::{
14    cell::RefCell,
15    future::Future,
16    pin::Pin,
17    rc::Rc,
18    sync::{
19        Arc, OnceLock,
20        atomic::{AtomicBool, Ordering},
21    },
22    task::{Context, Poll, Waker},
23    time::Duration,
24};
25
26#[cfg(target_arch = "wasm32")]
27use wasm_bindgen::JsCast;
28use web_time::Instant;
29
30use crate::{
31    hooks::{mutableStateOf, remember},
32    runtime::{RuntimeHandle, TaskHandle, current_runtime_handle},
33    state::{MutableState, State},
34};
35
36/// Spawns `future` on the current runtime's UI task queue.
37///
38/// Framework-internal: application code launches through a
39/// [`CoroutineScope`] so the work is cancelled with its
40/// composition. Returns `None` when there is no runtime on this thread.
41pub fn spawn_ui_task(future: impl Future<Output = ()> + 'static) -> Option<TaskHandle> {
42    current_runtime_handle().and_then(|runtime| runtime.spawn_ui(future))
43}
44
45/// A cancellation scope for work launched outside the composition pass.
46///
47/// Tasks launched through a scope are cancelled when the scope leaves the
48/// composition, so a click handler can start an asynchronous job without the
49/// job outliving the screen that started it.
50#[derive(Clone)]
51pub struct CoroutineScope {
52    inner: Rc<ScopeInner>,
53}
54
55struct ScopeInner {
56    runtime: Option<RuntimeHandle>,
57    tasks: RefCell<Vec<TaskHandle>>,
58}
59
60impl Drop for ScopeInner {
61    fn drop(&mut self) {
62        for task in self.tasks.borrow_mut().drain(..) {
63            task.cancel();
64        }
65    }
66}
67
68impl CoroutineScope {
69    /// Launches `future`, keeping it alive until it finishes or the scope is
70    /// cancelled.
71    pub fn launch(&self, future: impl Future<Output = ()> + 'static) {
72        let Some(runtime) = self.inner.runtime.clone() else {
73            log::warn!("cranpose: a coroutine scope with no runtime dropped its work");
74            return;
75        };
76        self.inner
77            .tasks
78            .borrow_mut()
79            .retain(|task| !task.is_finished());
80        if let Some(handle) = runtime.spawn_ui(future) {
81            self.inner.tasks.borrow_mut().push(handle);
82        }
83    }
84
85    /// Cancels every task this scope launched.
86    pub fn cancel(&self) {
87        for task in self.inner.tasks.borrow_mut().drain(..) {
88            task.cancel();
89        }
90    }
91
92    #[cfg(test)]
93    pub(crate) fn probe_identity(&self) -> usize {
94        Rc::as_ptr(&self.inner) as *const () as usize
95    }
96}
97
98/// Remembers a [`CoroutineScope`] bound to this position in the composition.
99#[allow(non_snake_case)]
100#[track_caller]
101pub fn rememberCoroutineScope() -> CoroutineScope {
102    remember(|| CoroutineScope {
103        inner: Rc::new(ScopeInner {
104            runtime: current_runtime_handle(),
105            tasks: RefCell::new(Vec::new()),
106        }),
107    })
108    .with(|scope| scope.clone())
109}
110
111/// Resolves after `duration` has elapsed.
112///
113/// The wait is served by the framework's timer, which posts the wake-up onto
114/// the runtime's UI queue. Nothing spins and no frames are requested while a
115/// delay is pending, so a one-minute timer costs nothing for a minute.
116pub fn delay(duration: Duration) -> Delay {
117    Delay {
118        deadline: Instant::now() + duration,
119        armed: false,
120        fired: Arc::new(AtomicBool::new(false)),
121    }
122}
123
124/// The future returned by [`delay`].
125pub struct Delay {
126    deadline: Instant,
127    armed: bool,
128    fired: Arc<AtomicBool>,
129}
130
131impl Future for Delay {
132    type Output = ();
133
134    fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<()> {
135        if self.fired.load(Ordering::Acquire) || Instant::now() >= self.deadline {
136            return Poll::Ready(());
137        }
138        let this = self.get_mut();
139        if !this.armed {
140            this.armed = true;
141            timer().arm(
142                this.deadline,
143                context.waker().clone(),
144                Arc::clone(&this.fired),
145            );
146        }
147        Poll::Pending
148    }
149}
150
151/// Runs `tick` every `period` until the returned future is dropped.
152///
153/// The first tick happens after one full period, matching a repeating timer
154/// rather than a leading-edge one.
155pub async fn interval(period: Duration, mut tick: impl FnMut()) {
156    loop {
157        delay(period).await;
158        tick();
159    }
160}
161
162#[cfg(not(target_arch = "wasm32"))]
163struct Alarm {
164    deadline: Instant,
165    waker: Waker,
166    fired: Arc<AtomicBool>,
167}
168
169struct Timer {
170    #[cfg(not(target_arch = "wasm32"))]
171    alarms: Mutex<Vec<Alarm>>,
172    #[cfg(not(target_arch = "wasm32"))]
173    wake: Condvar,
174}
175
176fn timer() -> &'static Timer {
177    static TIMER: OnceLock<&'static Timer> = OnceLock::new();
178    TIMER.get_or_init(|| {
179        let timer: &'static Timer = Box::leak(Box::new(Timer::new()));
180        timer.start();
181        timer
182    })
183}
184
185#[cfg(not(target_arch = "wasm32"))]
186impl Timer {
187    fn new() -> Self {
188        Self {
189            alarms: Mutex::new(Vec::new()),
190            wake: Condvar::new(),
191        }
192    }
193
194    fn start(&'static self) {
195        std::thread::Builder::new()
196            .name("cranpose-timer".to_string())
197            .spawn(move || self.run())
198            .expect("the timer thread starts");
199    }
200
201    fn run(&self) {
202        let mut alarms = self
203            .alarms
204            .lock()
205            .unwrap_or_else(|error| error.into_inner());
206        loop {
207            let now = Instant::now();
208            let mut due = Vec::new();
209            let mut next: Option<Duration> = None;
210            alarms.retain(|alarm| {
211                if alarm.deadline <= now {
212                    due.push((alarm.waker.clone(), Arc::clone(&alarm.fired)));
213                    false
214                } else {
215                    let remaining = alarm.deadline - now;
216                    next = Some(next.map_or(remaining, |current| current.min(remaining)));
217                    true
218                }
219            });
220
221            if !due.is_empty() {
222                drop(alarms);
223                for (waker, fired) in due {
224                    fired.store(true, Ordering::Release);
225                    waker.wake();
226                }
227                alarms = self
228                    .alarms
229                    .lock()
230                    .unwrap_or_else(|error| error.into_inner());
231                continue;
232            }
233
234            alarms = match next {
235                Some(timeout) => {
236                    self.wake
237                        .wait_timeout(alarms, timeout)
238                        .unwrap_or_else(|error| error.into_inner())
239                        .0
240                }
241                None => self
242                    .wake
243                    .wait(alarms)
244                    .unwrap_or_else(|error| error.into_inner()),
245            };
246        }
247    }
248
249    fn arm(&self, deadline: Instant, waker: Waker, fired: Arc<AtomicBool>) {
250        let mut alarms = self
251            .alarms
252            .lock()
253            .unwrap_or_else(|error| error.into_inner());
254        alarms.push(Alarm {
255            deadline,
256            waker,
257            fired,
258        });
259        self.wake.notify_one();
260    }
261}
262
263#[cfg(target_arch = "wasm32")]
264impl Timer {
265    fn new() -> Self {
266        Self {}
267    }
268
269    fn start(&'static self) {}
270
271    fn arm(&self, deadline: Instant, waker: Waker, fired: Arc<AtomicBool>) {
272        let millis = deadline
273            .saturating_duration_since(Instant::now())
274            .as_millis()
275            .min(i32::MAX as u128) as i32;
276        let callback = wasm_bindgen::closure::Closure::once_into_js(move || {
277            fired.store(true, Ordering::Release);
278            waker.wake();
279        });
280        let scheduled = web_sys::window().and_then(|window| {
281            window
282                .set_timeout_with_callback_and_timeout_and_arguments_0(
283                    callback.unchecked_ref(),
284                    millis,
285                )
286                .ok()
287        });
288        if scheduled.is_none() {
289            log::warn!("cranpose: no window timer is available; the delay resolves immediately");
290        }
291    }
292}
293
294/// The producing half of an [`EventStream`].
295///
296/// A service that receives events from outside the composition — a platform
297/// callback, a worker thread, a socket — publishes through a channel, and every
298/// pending collector is woken. This is the shape that replaces
299/// "register an observer, then drain a queue" everywhere in the framework.
300pub struct EventChannel<T: 'static> {
301    shared: Rc<ChannelShared<T>>,
302}
303
304struct ChannelShared<T: 'static> {
305    ready: RefCell<std::collections::VecDeque<T>>,
306    closed: std::cell::Cell<bool>,
307    delivered: std::cell::Cell<usize>,
308    wakers: RefCell<Vec<Waker>>,
309}
310
311impl<T: 'static> ChannelShared<T> {
312    fn wake_all(&self) {
313        for waker in self.wakers.borrow_mut().drain(..) {
314            waker.wake();
315        }
316    }
317}
318
319impl<T: 'static> Default for EventChannel<T> {
320    fn default() -> Self {
321        Self::new()
322    }
323}
324
325impl<T: 'static> EventChannel<T> {
326    /// Creates an open channel.
327    pub fn new() -> Self {
328        Self {
329            shared: Rc::new(ChannelShared {
330                ready: RefCell::new(std::collections::VecDeque::new()),
331                closed: std::cell::Cell::new(false),
332                delivered: std::cell::Cell::new(0),
333                wakers: RefCell::new(Vec::new()),
334            }),
335        }
336    }
337
338    /// The consuming half, handed to collectors.
339    pub fn stream(&self) -> EventStream<T> {
340        EventStream {
341            shared: Rc::clone(&self.shared),
342        }
343    }
344
345    /// Publishes one event and wakes every pending collector.
346    pub fn send(&self, event: T) {
347        if self.shared.closed.get() {
348            return;
349        }
350        self.shared.ready.borrow_mut().push_back(event);
351        self.shared.wake_all();
352    }
353
354    /// Ends the stream. Collectors drain what is queued and then finish.
355    pub fn close(&self) {
356        if self.shared.closed.get() {
357            return;
358        }
359        self.shared.closed.set(true);
360        self.shared.wake_all();
361    }
362
363    /// Whether the channel has been closed.
364    pub fn is_closed(&self) -> bool {
365        self.shared.closed.get()
366    }
367
368    /// How many events are queued but not yet taken.
369    pub fn pending(&self) -> usize {
370        self.shared.ready.borrow().len()
371    }
372}
373
374/// The consuming half of an [`EventChannel`].
375///
376/// Collectors take events one at a time; an event goes to exactly one
377/// collector, so two collectors share the stream rather than each seeing every
378/// event.
379pub struct EventStream<T: 'static> {
380    shared: Rc<ChannelShared<T>>,
381}
382
383impl<T: 'static> Clone for EventStream<T> {
384    fn clone(&self) -> Self {
385        Self {
386            shared: Rc::clone(&self.shared),
387        }
388    }
389}
390
391impl<T: 'static> EventStream<T> {
392    /// Resolves with the next event, or `None` once the stream is closed and
393    /// drained.
394    pub fn next(&self) -> EventStreamNext<T> {
395        EventStreamNext {
396            shared: Rc::clone(&self.shared),
397        }
398    }
399
400    /// How many events this stream has handed out.
401    pub fn delivered(&self) -> usize {
402        self.shared.delivered.get()
403    }
404}
405
406/// The future returned by [`EventStream::next`].
407pub struct EventStreamNext<T: 'static> {
408    shared: Rc<ChannelShared<T>>,
409}
410
411impl<T: 'static> Future for EventStreamNext<T> {
412    type Output = Option<T>;
413
414    fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<T>> {
415        if let Some(event) = self.shared.ready.borrow_mut().pop_front() {
416            self.shared.delivered.set(self.shared.delivered.get() + 1);
417            return Poll::Ready(Some(event));
418        }
419        if self.shared.closed.get() {
420            return Poll::Ready(None);
421        }
422        self.shared
423            .wakers
424            .borrow_mut()
425            .push(context.waker().clone());
426        Poll::Pending
427    }
428}
429
430/// Collects `stream` for as long as this call stays in the composition,
431/// handing each event to `on_event`.
432///
433/// `key` re-starts the collection when it changes, exactly like
434/// `LaunchedEffect`.
435#[allow(non_snake_case)]
436#[track_caller]
437pub fn CollectEvents<T, K>(stream: EventStream<T>, key: K, on_event: impl FnMut(T) + 'static)
438where
439    T: 'static,
440    K: PartialEq + 'static,
441{
442    crate::__launched_effect_async_impl(
443        crate::caller_location_key(),
444        std::panic::Location::caller().into(),
445        key,
446        move |_scope| {
447            let mut on_event = on_event;
448            Box::pin(async move {
449                while let Some(event) = stream.next().await {
450                    on_event(event);
451                }
452            })
453        },
454    );
455}
456
457/// Collects `stream` into state, starting at `initial`.
458///
459/// The composition reads the latest value the stream produced, and recomposes
460/// when a new one arrives.
461#[allow(non_snake_case)]
462#[track_caller]
463pub fn collectAsState<T, K>(stream: EventStream<T>, key: K, initial: T) -> State<T>
464where
465    T: Clone + 'static,
466    K: PartialEq + 'static,
467{
468    let state = remember(|| mutableStateOf(initial)).with(|state| *state);
469    let sink = state;
470    CollectEvents(stream, key, move |event| sink.set(event));
471    state.as_state()
472}
473
474/// A `Send` publishing handle for a composition-scoped [`EventStream`].
475///
476/// Platform services publish events from whatever thread they run on — a JNI
477/// callback, a worker, a socket reader. The sender hops each event onto the UI
478/// thread through the runtime's dispatcher and pushes it into the stream the
479/// composition is collecting, so no service and no application ever writes that
480/// hop again.
481pub struct EventSender<T: Send + 'static> {
482    #[cfg(not(target_arch = "wasm32"))]
483    dispatcher: crate::runtime::UiDispatcher,
484    bridge: u64,
485    _events: std::marker::PhantomData<fn(T)>,
486}
487
488impl<T: Send + 'static> Clone for EventSender<T> {
489    fn clone(&self) -> Self {
490        Self {
491            #[cfg(not(target_arch = "wasm32"))]
492            dispatcher: self.dispatcher.clone(),
493            bridge: self.bridge,
494            _events: std::marker::PhantomData,
495        }
496    }
497}
498
499impl<T: Send + 'static> EventSender<T> {
500    /// Publishes `event` to the composition that owns this bridge.
501    pub fn send(&self, event: T) {
502        let bridge = self.bridge;
503        #[cfg(not(target_arch = "wasm32"))]
504        self.dispatcher
505            .post(move || deliver_bridged::<T>(bridge, event));
506        #[cfg(target_arch = "wasm32")]
507        deliver_bridged::<T>(bridge, event);
508    }
509}
510
511thread_local! {
512    static BRIDGES: RefCell<std::collections::HashMap<u64, Rc<dyn std::any::Any>>> =
513        RefCell::new(std::collections::HashMap::new());
514}
515
516static NEXT_BRIDGE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
517
518fn deliver_bridged<T: Send + 'static>(bridge: u64, event: T) {
519    let channel = BRIDGES.with(|bridges| bridges.borrow().get(&bridge).cloned());
520    let Some(channel) = channel else {
521        log::debug!("event bridge {bridge} is gone, one event dropped");
522        return;
523    };
524    if let Ok(channel) = channel.downcast::<EventChannel<T>>() {
525        channel.send(event);
526    }
527}
528
529struct Bridge<T: Send + 'static> {
530    id: u64,
531    channel: Rc<EventChannel<T>>,
532}
533
534impl<T: Send + 'static> Bridge<T> {
535    fn new() -> Self {
536        let id = NEXT_BRIDGE.fetch_add(1, Ordering::Relaxed);
537        let channel = Rc::new(EventChannel::<T>::new());
538        BRIDGES.with(|bridges| {
539            bridges
540                .borrow_mut()
541                .insert(id, Rc::clone(&channel) as Rc<dyn std::any::Any>)
542        });
543        Self { id, channel }
544    }
545}
546
547impl<T: Send + 'static> Drop for Bridge<T> {
548    fn drop(&mut self) {
549        BRIDGES.with(|bridges| bridges.borrow_mut().remove(&self.id));
550        self.channel.close();
551    }
552}
553
554/// Turns a platform subscription into a composition-scoped [`EventStream`].
555///
556/// `subscribe` receives a `Send` [`EventSender`] and returns whatever
557/// registration handle the service uses; that handle is dropped — unsubscribing
558/// the service — when `key` changes or the composition leaves. This is the one
559/// place the framework bridges "a service publishes from another thread" to
560/// "a composition collects".
561#[allow(non_snake_case)]
562#[track_caller]
563pub fn rememberEventStream<T, K, R, S>(key: K, subscribe: S) -> EventStream<T>
564where
565    T: Send + 'static,
566    K: PartialEq + 'static,
567    R: 'static,
568    S: FnOnce(EventSender<T>) -> R + 'static,
569{
570    let bridge = remember(Bridge::<T>::new);
571    let (id, stream) = bridge.with(|bridge| (bridge.id, bridge.channel.stream()));
572    #[cfg(not(target_arch = "wasm32"))]
573    let dispatcher = current_runtime_handle().map(|runtime| runtime.dispatcher());
574
575    crate::__disposable_effect_impl(crate::caller_location_key(), key, move |scope| {
576        #[cfg(not(target_arch = "wasm32"))]
577        let Some(dispatcher) = dispatcher else {
578            log::warn!("cranpose: an event stream was remembered without a runtime");
579            return scope.on_dispose(|| {});
580        };
581        let registration = subscribe(EventSender {
582            #[cfg(not(target_arch = "wasm32"))]
583            dispatcher,
584            bridge: id,
585            _events: std::marker::PhantomData,
586        });
587        scope.on_dispose(move || drop(registration))
588    });
589
590    stream
591}
592
593/// Runs `work` off the UI thread and resolves with its result on the UI thread.
594///
595/// This is the escape hatch for genuinely blocking work — parsing a large file,
596/// a synchronous provider call — that must not stall composition. On the web
597/// there is one thread, so `work` runs inline; callers keep the unit of work
598/// small enough that this is honest on every target.
599#[allow(non_snake_case)]
600pub async fn withBlocking<T, F>(work: F) -> T
601where
602    T: Send + 'static,
603    F: FnOnce() -> T + Send + 'static,
604{
605    #[cfg(not(target_arch = "wasm32"))]
606    {
607        let slot: Arc<Mutex<Option<T>>> = Arc::new(Mutex::new(None));
608        let done = Arc::new(AtomicBool::new(false));
609        let wakers: Arc<Mutex<Vec<Waker>>> = Arc::new(Mutex::new(Vec::new()));
610
611        let worker_slot = Arc::clone(&slot);
612        let worker_done = Arc::clone(&done);
613        let worker_wakers = Arc::clone(&wakers);
614        BlockingPool::get().submit(Box::new(move || {
615            let value = work();
616            *worker_slot
617                .lock()
618                .unwrap_or_else(|error| error.into_inner()) = Some(value);
619            worker_done.store(true, Ordering::Release);
620            for waker in worker_wakers
621                .lock()
622                .unwrap_or_else(|error| error.into_inner())
623                .drain(..)
624            {
625                waker.wake();
626            }
627        }));
628
629        BlockingWork { slot, done, wakers }.await
630    }
631    #[cfg(target_arch = "wasm32")]
632    {
633        work()
634    }
635}
636
637/// Runs `work` off the UI thread and hands its result to `on_ui` on the UI
638/// thread.
639///
640/// The callback shape of [`withBlocking`], for the code that is not already in
641/// a coroutine: an event handler, a button, anything that wants to start some
642/// blocking work and carry on. Both share the same pool, so an application
643/// that uses one, the other, or both never spends more than one set of threads
644/// on blocking work.
645///
646/// Without a runtime — a unit test, a tool — `work` runs inline and `on_ui`
647/// follows it, so a caller behaves the same either way.
648///
649/// ```rust,ignore
650/// launchBlocking(
651///     move || std::fs::read(path),
652///     move |bytes| document.set(bytes.ok()),
653/// );
654/// ```
655#[allow(non_snake_case)]
656pub fn launchBlocking<T>(work: impl FnOnce() -> T + Send + 'static, on_ui: impl FnOnce(T) + 'static)
657where
658    T: Send + 'static,
659{
660    let Some(runtime) = current_runtime_handle() else {
661        on_ui(work());
662        return;
663    };
664    let Some(continuation) = runtime.register_ui_cont(on_ui) else {
665        return;
666    };
667    let dispatcher = runtime.dispatcher();
668    #[cfg(not(target_arch = "wasm32"))]
669    BlockingPool::get().submit(Box::new(move || {
670        dispatcher.post_invoke(continuation, work());
671    }));
672    #[cfg(target_arch = "wasm32")]
673    dispatcher.post_invoke(continuation, work());
674}
675
676#[cfg(not(target_arch = "wasm32"))]
677struct BlockingPool {
678    sender: std::sync::mpsc::Sender<BlockingJob>,
679    receiver: Arc<Mutex<std::sync::mpsc::Receiver<BlockingJob>>>,
680    state: Arc<Mutex<PoolState>>,
681}
682
683#[cfg(not(target_arch = "wasm32"))]
684#[derive(Clone, Copy, Default)]
685struct PoolState {
686    alive: usize,
687    outstanding: usize,
688}
689
690#[cfg(not(target_arch = "wasm32"))]
691type BlockingJob = Box<dyn FnOnce() + Send + 'static>;
692
693#[cfg(not(target_arch = "wasm32"))]
694const MAX_BLOCKING_WORKERS: usize = 64;
695
696#[cfg(not(target_arch = "wasm32"))]
697const _: () = assert!(MAX_BLOCKING_WORKERS > 0 && MAX_BLOCKING_WORKERS <= 256);
698
699#[cfg(not(target_arch = "wasm32"))]
700impl BlockingPool {
701    fn get() -> &'static BlockingPool {
702        static POOL: OnceLock<BlockingPool> = OnceLock::new();
703        POOL.get_or_init(BlockingPool::new)
704    }
705
706    fn new() -> BlockingPool {
707        let (sender, receiver) = std::sync::mpsc::channel();
708        BlockingPool {
709            sender,
710            receiver: Arc::new(Mutex::new(receiver)),
711            state: Arc::new(Mutex::new(PoolState::default())),
712        }
713    }
714
715    fn submit(&self, job: BlockingJob) {
716        if self.take_slot() {
717            self.start_worker();
718        }
719        if let Err(returned) = self.sender.send(job) {
720            self.release_slot();
721            (returned.0)();
722        }
723    }
724
725    fn take_slot(&self) -> bool {
726        let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner());
727        state.outstanding += 1;
728        let grow = state.alive < state.outstanding && state.alive < MAX_BLOCKING_WORKERS;
729        if grow {
730            state.alive += 1;
731        }
732        grow
733    }
734
735    fn release_slot(&self) {
736        let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner());
737        state.outstanding = state.outstanding.saturating_sub(1);
738    }
739
740    fn start_worker(&self) {
741        let receiver = Arc::clone(&self.receiver);
742        let counters = Arc::clone(&self.state);
743        let started = std::thread::Builder::new()
744            .name("cranpose-blocking".to_string())
745            .spawn(move || {
746                loop {
747                    let job = {
748                        let queue = receiver.lock().unwrap_or_else(|error| error.into_inner());
749                        queue.recv()
750                    };
751                    let Ok(job) = job else {
752                        break;
753                    };
754                    job();
755                    let mut counters = counters.lock().unwrap_or_else(|error| error.into_inner());
756                    counters.outstanding = counters.outstanding.saturating_sub(1);
757                }
758            });
759        if started.is_err() {
760            let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner());
761            state.alive -= 1;
762        }
763    }
764}
765
766#[cfg(not(target_arch = "wasm32"))]
767struct BlockingWork<T> {
768    slot: Arc<Mutex<Option<T>>>,
769    done: Arc<AtomicBool>,
770    wakers: Arc<Mutex<Vec<Waker>>>,
771}
772
773#[cfg(not(target_arch = "wasm32"))]
774impl<T> Future for BlockingWork<T> {
775    type Output = T;
776
777    fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<T> {
778        if self.done.load(Ordering::Acquire)
779            && let Some(value) = self
780                .slot
781                .lock()
782                .unwrap_or_else(|error| error.into_inner())
783                .take()
784        {
785            return Poll::Ready(value);
786        }
787        self.wakers
788            .lock()
789            .unwrap_or_else(|error| error.into_inner())
790            .push(context.waker().clone());
791        if self.done.load(Ordering::Acquire)
792            && let Some(value) = self
793                .slot
794                .lock()
795                .unwrap_or_else(|error| error.into_inner())
796                .take()
797        {
798            return Poll::Ready(value);
799        }
800        Poll::Pending
801    }
802}
803
804/// Runs `producer` when `key` changes and exposes what it publishes as state.
805///
806/// The Compose `produceState` contract: the producer receives a handle it uses
807/// to publish values, and is cancelled when the key changes or the composition
808/// leaves.
809#[allow(non_snake_case)]
810#[track_caller]
811pub fn produceState<T, K, F>(initial: T, key: K, producer: F) -> State<T>
812where
813    T: Clone + 'static,
814    K: PartialEq + 'static,
815    F: FnOnce(ProduceScope<T>) -> Pin<Box<dyn Future<Output = ()>>> + 'static,
816{
817    let state = remember(|| mutableStateOf(initial)).with(|state| *state);
818    let handle = ProduceScope { state };
819    crate::__launched_effect_async_impl(
820        crate::caller_location_key(),
821        std::panic::Location::caller().into(),
822        key,
823        move |_scope| producer(handle),
824    );
825    state.as_state()
826}
827
828/// The publishing half handed to a [`produceState`] producer.
829pub struct ProduceScope<T: Clone + 'static> {
830    state: MutableState<T>,
831}
832
833impl<T: Clone + 'static> ProduceScope<T> {
834    /// Publishes `value` to the produced state.
835    pub fn set(&self, value: T) {
836        self.state.set(value);
837    }
838}
839
840#[cfg(test)]
841mod tests {
842    use super::*;
843
844    #[test]
845    fn a_delay_resolves_after_its_deadline() {
846        let started = Instant::now();
847        pollster::block_on(delay(Duration::from_millis(30)));
848        assert!(started.elapsed() >= Duration::from_millis(25));
849    }
850
851    #[test]
852    fn many_delays_share_one_timer_and_all_fire() {
853        let started = Instant::now();
854        pollster::block_on(async {
855            for _ in 0..4 {
856                delay(Duration::from_millis(5)).await;
857            }
858        });
859        assert!(started.elapsed() >= Duration::from_millis(15));
860    }
861
862    #[test]
863    fn an_elapsed_delay_is_ready_without_arming_the_timer() {
864        let mut future = Box::pin(Delay {
865            deadline: Instant::now() - Duration::from_millis(1),
866            armed: false,
867            fired: Arc::new(AtomicBool::new(false)),
868        });
869        let waker = Waker::noop().clone();
870        assert!(
871            future
872                .as_mut()
873                .poll(&mut Context::from_waker(&waker))
874                .is_ready()
875        );
876    }
877}
878
879#[cfg(test)]
880mod stream_tests {
881    use super::*;
882
883    #[test]
884    fn a_channel_wakes_its_collector_and_ends_when_closed() {
885        let channel: EventChannel<u32> = EventChannel::new();
886        let stream = channel.stream();
887
888        let mut pending = Box::pin(stream.next());
889        let waker = Waker::noop().clone();
890        let mut context = Context::from_waker(&waker);
891        assert!(pending.as_mut().poll(&mut context).is_pending());
892
893        channel.send(7);
894        assert_eq!(pending.as_mut().poll(&mut context), Poll::Ready(Some(7)));
895
896        channel.send(8);
897        channel.close();
898        assert_eq!(pollster::block_on(stream.next()), Some(8));
899        assert_eq!(pollster::block_on(stream.next()), None);
900        assert_eq!(stream.delivered(), 2);
901    }
902
903    #[test]
904    fn an_event_goes_to_exactly_one_collector() {
905        let channel: EventChannel<u32> = EventChannel::new();
906        let first = channel.stream();
907        let second = first.clone();
908        channel.send(1);
909        channel.close();
910        assert_eq!(pollster::block_on(first.next()), Some(1));
911        assert_eq!(pollster::block_on(second.next()), None);
912    }
913
914    #[test]
915    fn sending_after_close_is_ignored() {
916        let channel: EventChannel<u32> = EventChannel::new();
917        let stream = channel.stream();
918        channel.close();
919        channel.send(1);
920        assert_eq!(pollster::block_on(stream.next()), None);
921        assert_eq!(channel.pending(), 0);
922    }
923
924    #[test]
925    fn blocking_work_resolves_with_its_result() {
926        let doubled = pollster::block_on(withBlocking(|| 21 * 2));
927        assert_eq!(doubled, 42);
928    }
929}
930
931#[cfg(test)]
932mod timer_race_tests {
933    use super::*;
934
935    #[test]
936    fn concurrent_arming_never_loses_a_wake_up() {
937        let rounds = 40;
938        let threads: Vec<_> = (0..8)
939            .map(|worker| {
940                std::thread::spawn(move || {
941                    for round in 0..rounds {
942                        let millis = 1 + ((worker + round) % 5) as u64;
943                        pollster::block_on(delay(Duration::from_millis(millis)));
944                    }
945                })
946            })
947            .collect();
948        for thread in threads {
949            thread.join().expect("every waiter is woken");
950        }
951    }
952
953    #[cfg(not(target_arch = "wasm32"))]
954    #[test]
955    fn blocking_work_reuses_its_threads_instead_of_one_per_call() {
956        use std::{collections::HashSet, sync::mpsc};
957
958        let pool = BlockingPool::new();
959
960        let (sender, receiver) = mpsc::channel();
961        for _ in 0..16 {
962            let done = Arc::new((Mutex::new(false), Condvar::new()));
963            let waiter = Arc::clone(&done);
964            let sender = sender.clone();
965            pool.submit(Box::new(move || {
966                let _ = sender.send(std::thread::current().id());
967                let (lock, signal) = &*done;
968                *lock.lock().unwrap_or_else(|error| error.into_inner()) = true;
969                signal.notify_all();
970            }));
971            let (lock, signal) = &*waiter;
972            let mut finished = lock.lock().unwrap_or_else(|error| error.into_inner());
973            while !*finished {
974                finished = signal
975                    .wait(finished)
976                    .unwrap_or_else(|error| error.into_inner());
977            }
978        }
979        drop(sender);
980
981        let threads: HashSet<_> = receiver.iter().collect();
982        assert!(
983            threads.len() < 16,
984            "sixteen serial jobs used {} threads; the pool is not reusing them",
985            threads.len()
986        );
987    }
988
989    #[cfg(not(target_arch = "wasm32"))]
990    #[test]
991    fn blocking_work_grows_so_one_slow_job_cannot_hold_up_another() {
992        let pool = BlockingPool::new();
993        let started = Arc::new((Mutex::new(0usize), Condvar::new()));
994        let release = Arc::new((Mutex::new(false), Condvar::new()));
995
996        for _ in 0..4 {
997            let started = Arc::clone(&started);
998            let release = Arc::clone(&release);
999            pool.submit(Box::new(move || {
1000                {
1001                    let (count, signal) = &*started;
1002                    *count.lock().unwrap_or_else(|error| error.into_inner()) += 1;
1003                    signal.notify_all();
1004                }
1005                let (held, signal) = &*release;
1006                let mut go = held.lock().unwrap_or_else(|error| error.into_inner());
1007                while !*go {
1008                    go = signal.wait(go).unwrap_or_else(|error| error.into_inner());
1009                }
1010            }));
1011        }
1012
1013        let (count, signal) = &*started;
1014        let mut running = count.lock().unwrap_or_else(|error| error.into_inner());
1015        while *running < 4 {
1016            running = signal
1017                .wait(running)
1018                .unwrap_or_else(|error| error.into_inner());
1019        }
1020        drop(running);
1021
1022        let (held, signal) = &*release;
1023        *held.lock().unwrap_or_else(|error| error.into_inner()) = true;
1024        signal.notify_all();
1025    }
1026}