1#[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 atomic::{AtomicBool, Ordering},
20 Arc, OnceLock,
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::{current_runtime_handle, RuntimeHandle, TaskHandle},
33 state::{MutableState, State},
34};
35
36pub 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#[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 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
79 .tasks
80 .borrow_mut()
81 .retain(|task| !task.is_finished());
82 if let Some(handle) = runtime.spawn_ui(future) {
83 self.inner.tasks.borrow_mut().push(handle);
84 }
85 }
86
87 pub fn cancel(&self) {
89 for task in self.inner.tasks.borrow_mut().drain(..) {
90 task.cancel();
91 }
92 }
93}
94
95#[allow(non_snake_case)]
97pub fn rememberCoroutineScope() -> CoroutineScope {
98 remember(|| CoroutineScope {
99 inner: Rc::new(ScopeInner {
100 runtime: current_runtime_handle(),
101 tasks: RefCell::new(Vec::new()),
102 }),
103 })
104 .with(|scope| scope.clone())
105}
106
107pub fn delay(duration: Duration) -> Delay {
115 Delay {
116 deadline: Instant::now() + duration,
117 armed: false,
118 fired: Arc::new(AtomicBool::new(false)),
119 }
120}
121
122pub struct Delay {
124 deadline: Instant,
125 armed: bool,
126 fired: Arc<AtomicBool>,
127}
128
129impl Future for Delay {
130 type Output = ();
131
132 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<()> {
133 if self.fired.load(Ordering::Acquire) || Instant::now() >= self.deadline {
134 return Poll::Ready(());
135 }
136 let this = self.get_mut();
137 if !this.armed {
138 this.armed = true;
139 timer().arm(
140 this.deadline,
141 context.waker().clone(),
142 Arc::clone(&this.fired),
143 );
144 }
145 Poll::Pending
146 }
147}
148
149pub async fn interval(period: Duration, mut tick: impl FnMut()) {
154 loop {
155 delay(period).await;
156 tick();
157 }
158}
159
160#[cfg(not(target_arch = "wasm32"))]
162struct Alarm {
163 deadline: Instant,
164 waker: Waker,
165 fired: Arc<AtomicBool>,
166}
167
168struct Timer {
175 #[cfg(not(target_arch = "wasm32"))]
176 alarms: Mutex<Vec<Alarm>>,
177 #[cfg(not(target_arch = "wasm32"))]
178 wake: Condvar,
179}
180
181fn timer() -> &'static Timer {
182 static TIMER: OnceLock<&'static Timer> = OnceLock::new();
183 TIMER.get_or_init(|| {
184 let timer: &'static Timer = Box::leak(Box::new(Timer::new()));
185 timer.start();
186 timer
187 })
188}
189
190#[cfg(not(target_arch = "wasm32"))]
191impl Timer {
192 fn new() -> Self {
193 Self {
194 alarms: Mutex::new(Vec::new()),
195 wake: Condvar::new(),
196 }
197 }
198
199 fn start(&'static self) {
200 std::thread::Builder::new()
201 .name("cranpose-timer".to_string())
202 .spawn(move || self.run())
203 .expect("the timer thread starts");
204 }
205
206 fn run(&self) {
215 let mut alarms = self
216 .alarms
217 .lock()
218 .unwrap_or_else(|error| error.into_inner());
219 loop {
220 let now = Instant::now();
221 let mut due = Vec::new();
222 let mut next: Option<Duration> = None;
223 alarms.retain(|alarm| {
224 if alarm.deadline <= now {
225 due.push((alarm.waker.clone(), Arc::clone(&alarm.fired)));
226 false
227 } else {
228 let remaining = alarm.deadline - now;
229 next = Some(next.map_or(remaining, |current| current.min(remaining)));
230 true
231 }
232 });
233
234 if !due.is_empty() {
235 drop(alarms);
238 for (waker, fired) in due {
239 fired.store(true, Ordering::Release);
240 waker.wake();
241 }
242 alarms = self
243 .alarms
244 .lock()
245 .unwrap_or_else(|error| error.into_inner());
246 continue;
247 }
248
249 alarms = match next {
250 Some(timeout) => {
251 self.wake
252 .wait_timeout(alarms, timeout)
253 .unwrap_or_else(|error| error.into_inner())
254 .0
255 }
256 None => self
257 .wake
258 .wait(alarms)
259 .unwrap_or_else(|error| error.into_inner()),
260 };
261 }
262 }
263
264 fn arm(&self, deadline: Instant, waker: Waker, fired: Arc<AtomicBool>) {
265 let mut alarms = self
266 .alarms
267 .lock()
268 .unwrap_or_else(|error| error.into_inner());
269 alarms.push(Alarm {
270 deadline,
271 waker,
272 fired,
273 });
274 self.wake.notify_one();
275 }
276}
277
278#[cfg(target_arch = "wasm32")]
279impl Timer {
280 fn new() -> Self {
281 Self {}
282 }
283
284 fn start(&'static self) {}
285
286 fn arm(&self, deadline: Instant, waker: Waker, fired: Arc<AtomicBool>) {
287 let millis = deadline
288 .saturating_duration_since(Instant::now())
289 .as_millis()
290 .min(i32::MAX as u128) as i32;
291 let callback = wasm_bindgen::closure::Closure::once_into_js(move || {
292 fired.store(true, Ordering::Release);
293 waker.wake();
294 });
295 let scheduled = web_sys::window().and_then(|window| {
296 window
297 .set_timeout_with_callback_and_timeout_and_arguments_0(
298 callback.unchecked_ref(),
299 millis,
300 )
301 .ok()
302 });
303 if scheduled.is_none() {
304 log::warn!("cranpose: no window timer is available; the delay resolves immediately");
305 }
306 }
307}
308
309pub struct EventChannel<T: 'static> {
318 shared: Rc<ChannelShared<T>>,
319}
320
321struct ChannelShared<T: 'static> {
322 ready: RefCell<std::collections::VecDeque<T>>,
323 closed: std::cell::Cell<bool>,
324 delivered: std::cell::Cell<usize>,
325 wakers: RefCell<Vec<Waker>>,
326}
327
328impl<T: 'static> ChannelShared<T> {
329 fn wake_all(&self) {
330 for waker in self.wakers.borrow_mut().drain(..) {
331 waker.wake();
332 }
333 }
334}
335
336impl<T: 'static> Default for EventChannel<T> {
337 fn default() -> Self {
338 Self::new()
339 }
340}
341
342impl<T: 'static> EventChannel<T> {
343 pub fn new() -> Self {
345 Self {
346 shared: Rc::new(ChannelShared {
347 ready: RefCell::new(std::collections::VecDeque::new()),
348 closed: std::cell::Cell::new(false),
349 delivered: std::cell::Cell::new(0),
350 wakers: RefCell::new(Vec::new()),
351 }),
352 }
353 }
354
355 pub fn stream(&self) -> EventStream<T> {
357 EventStream {
358 shared: Rc::clone(&self.shared),
359 }
360 }
361
362 pub fn send(&self, event: T) {
364 if self.shared.closed.get() {
365 return;
366 }
367 self.shared.ready.borrow_mut().push_back(event);
368 self.shared.wake_all();
369 }
370
371 pub fn close(&self) {
373 if self.shared.closed.get() {
374 return;
375 }
376 self.shared.closed.set(true);
377 self.shared.wake_all();
378 }
379
380 pub fn is_closed(&self) -> bool {
382 self.shared.closed.get()
383 }
384
385 pub fn pending(&self) -> usize {
387 self.shared.ready.borrow().len()
388 }
389}
390
391pub struct EventStream<T: 'static> {
397 shared: Rc<ChannelShared<T>>,
398}
399
400impl<T: 'static> Clone for EventStream<T> {
401 fn clone(&self) -> Self {
402 Self {
403 shared: Rc::clone(&self.shared),
404 }
405 }
406}
407
408impl<T: 'static> EventStream<T> {
409 pub fn next(&self) -> EventStreamNext<T> {
412 EventStreamNext {
413 shared: Rc::clone(&self.shared),
414 }
415 }
416
417 pub fn delivered(&self) -> usize {
419 self.shared.delivered.get()
420 }
421}
422
423pub struct EventStreamNext<T: 'static> {
425 shared: Rc<ChannelShared<T>>,
426}
427
428impl<T: 'static> Future for EventStreamNext<T> {
429 type Output = Option<T>;
430
431 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<T>> {
432 if let Some(event) = self.shared.ready.borrow_mut().pop_front() {
433 self.shared.delivered.set(self.shared.delivered.get() + 1);
434 return Poll::Ready(Some(event));
435 }
436 if self.shared.closed.get() {
437 return Poll::Ready(None);
438 }
439 self.shared
440 .wakers
441 .borrow_mut()
442 .push(context.waker().clone());
443 Poll::Pending
444 }
445}
446
447#[allow(non_snake_case)]
453#[track_caller]
454pub fn CollectEvents<T, K>(stream: EventStream<T>, key: K, on_event: impl FnMut(T) + 'static)
455where
456 T: 'static,
457 K: PartialEq + 'static,
458{
459 crate::__launched_effect_async_impl(
460 crate::location_key(file!(), line!(), column!()),
461 std::panic::Location::caller().into(),
462 key,
463 move |_scope| {
464 let mut on_event = on_event;
465 Box::pin(async move {
466 while let Some(event) = stream.next().await {
467 on_event(event);
468 }
469 })
470 },
471 );
472}
473
474#[allow(non_snake_case)]
479#[track_caller]
480pub fn collectAsState<T, K>(stream: EventStream<T>, key: K, initial: T) -> State<T>
481where
482 T: Clone + 'static,
483 K: PartialEq + 'static,
484{
485 let state = remember(|| mutableStateOf(initial)).with(|state| *state);
486 let sink = state;
487 CollectEvents(stream, key, move |event| sink.set(event));
488 state.as_state()
489}
490
491pub struct EventSender<T: Send + 'static> {
501 #[cfg(not(target_arch = "wasm32"))]
504 dispatcher: crate::runtime::UiDispatcher,
505 bridge: u64,
506 _events: std::marker::PhantomData<fn(T)>,
507}
508
509impl<T: Send + 'static> Clone for EventSender<T> {
510 fn clone(&self) -> Self {
511 Self {
512 #[cfg(not(target_arch = "wasm32"))]
513 dispatcher: self.dispatcher.clone(),
514 bridge: self.bridge,
515 _events: std::marker::PhantomData,
516 }
517 }
518}
519
520impl<T: Send + 'static> EventSender<T> {
521 pub fn send(&self, event: T) {
523 let bridge = self.bridge;
524 #[cfg(not(target_arch = "wasm32"))]
525 self.dispatcher
526 .post(move || deliver_bridged::<T>(bridge, event));
527 #[cfg(target_arch = "wasm32")]
528 deliver_bridged::<T>(bridge, event);
529 }
530}
531
532thread_local! {
533 static BRIDGES: RefCell<std::collections::HashMap<u64, Rc<dyn std::any::Any>>> =
536 RefCell::new(std::collections::HashMap::new());
537}
538
539static NEXT_BRIDGE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
540
541fn deliver_bridged<T: Send + 'static>(bridge: u64, event: T) {
542 let channel = BRIDGES.with(|bridges| bridges.borrow().get(&bridge).cloned());
543 let Some(channel) = channel else {
544 return;
547 };
548 if let Ok(channel) = channel.downcast::<EventChannel<T>>() {
549 channel.send(event);
550 }
551}
552
553struct Bridge<T: Send + 'static> {
556 id: u64,
557 channel: Rc<EventChannel<T>>,
558}
559
560impl<T: Send + 'static> Bridge<T> {
561 fn new() -> Self {
562 let id = NEXT_BRIDGE.fetch_add(1, Ordering::Relaxed);
563 let channel = Rc::new(EventChannel::<T>::new());
564 BRIDGES.with(|bridges| {
565 bridges
566 .borrow_mut()
567 .insert(id, Rc::clone(&channel) as Rc<dyn std::any::Any>)
568 });
569 Self { id, channel }
570 }
571}
572
573impl<T: Send + 'static> Drop for Bridge<T> {
574 fn drop(&mut self) {
575 BRIDGES.with(|bridges| bridges.borrow_mut().remove(&self.id));
576 self.channel.close();
577 }
578}
579
580#[allow(non_snake_case)]
588pub fn rememberEventStream<T, K, R, S>(key: K, subscribe: S) -> EventStream<T>
589where
590 T: Send + 'static,
591 K: PartialEq + 'static,
592 R: 'static,
593 S: FnOnce(EventSender<T>) -> R + 'static,
594{
595 let bridge = remember(Bridge::<T>::new);
596 let (id, stream) = bridge.with(|bridge| (bridge.id, bridge.channel.stream()));
597 #[cfg(not(target_arch = "wasm32"))]
598 let dispatcher = current_runtime_handle().map(|runtime| runtime.dispatcher());
599
600 crate::__disposable_effect_impl(
601 crate::location_key(file!(), line!(), column!()),
602 key,
603 move |scope| {
604 #[cfg(not(target_arch = "wasm32"))]
605 let Some(dispatcher) = dispatcher
606 else {
607 log::warn!("cranpose: an event stream was remembered without a runtime");
608 return scope.on_dispose(|| {});
609 };
610 let registration = subscribe(EventSender {
611 #[cfg(not(target_arch = "wasm32"))]
612 dispatcher,
613 bridge: id,
614 _events: std::marker::PhantomData,
615 });
616 scope.on_dispose(move || drop(registration))
617 },
618 );
619
620 stream
621}
622
623#[allow(non_snake_case)]
632pub async fn withBlocking<T, F>(work: F) -> T
633where
634 T: Send + 'static,
635 F: FnOnce() -> T + Send + 'static,
636{
637 #[cfg(not(target_arch = "wasm32"))]
638 {
639 let slot: Arc<Mutex<Option<T>>> = Arc::new(Mutex::new(None));
640 let done = Arc::new(AtomicBool::new(false));
641 let wakers: Arc<Mutex<Vec<Waker>>> = Arc::new(Mutex::new(Vec::new()));
642
643 let worker_slot = Arc::clone(&slot);
644 let worker_done = Arc::clone(&done);
645 let worker_wakers = Arc::clone(&wakers);
646 BlockingPool::get().submit(Box::new(move || {
647 let value = work();
648 *worker_slot
649 .lock()
650 .unwrap_or_else(|error| error.into_inner()) = Some(value);
651 worker_done.store(true, Ordering::Release);
652 for waker in worker_wakers
653 .lock()
654 .unwrap_or_else(|error| error.into_inner())
655 .drain(..)
656 {
657 waker.wake();
658 }
659 }));
660
661 BlockingWork { slot, done, wakers }.await
662 }
663 #[cfg(target_arch = "wasm32")]
664 {
665 work()
666 }
667}
668
669#[allow(non_snake_case)]
688pub fn launchBlocking<T>(work: impl FnOnce() -> T + Send + 'static, on_ui: impl FnOnce(T) + 'static)
689where
690 T: Send + 'static,
691{
692 let Some(runtime) = current_runtime_handle() else {
693 on_ui(work());
694 return;
695 };
696 let Some(continuation) = runtime.register_ui_cont(on_ui) else {
697 return;
698 };
699 let dispatcher = runtime.dispatcher();
700 #[cfg(not(target_arch = "wasm32"))]
701 BlockingPool::get().submit(Box::new(move || {
702 dispatcher.post_invoke(continuation, work());
703 }));
704 #[cfg(target_arch = "wasm32")]
705 dispatcher.post_invoke(continuation, work());
706}
707
708#[cfg(not(target_arch = "wasm32"))]
721struct BlockingPool {
722 sender: std::sync::mpsc::Sender<BlockingJob>,
723 receiver: Arc<Mutex<std::sync::mpsc::Receiver<BlockingJob>>>,
724 state: Arc<Mutex<PoolState>>,
725}
726
727#[cfg(not(target_arch = "wasm32"))]
735#[derive(Clone, Copy, Default)]
736struct PoolState {
737 alive: usize,
738 outstanding: usize,
739}
740
741#[cfg(not(target_arch = "wasm32"))]
742type BlockingJob = Box<dyn FnOnce() + Send + 'static>;
743
744#[cfg(not(target_arch = "wasm32"))]
748const MAX_BLOCKING_WORKERS: usize = 64;
749
750#[cfg(not(target_arch = "wasm32"))]
754const _: () = assert!(MAX_BLOCKING_WORKERS > 0 && MAX_BLOCKING_WORKERS <= 256);
755
756#[cfg(not(target_arch = "wasm32"))]
757impl BlockingPool {
758 fn get() -> &'static BlockingPool {
759 static POOL: OnceLock<BlockingPool> = OnceLock::new();
760 POOL.get_or_init(BlockingPool::new)
761 }
762
763 fn new() -> BlockingPool {
764 let (sender, receiver) = std::sync::mpsc::channel();
765 BlockingPool {
766 sender,
767 receiver: Arc::new(Mutex::new(receiver)),
768 state: Arc::new(Mutex::new(PoolState::default())),
769 }
770 }
771
772 fn submit(&self, job: BlockingJob) {
773 if self.take_slot() {
774 self.start_worker();
775 }
776 if let Err(returned) = self.sender.send(job) {
779 self.release_slot();
780 (returned.0)();
781 }
782 }
783
784 fn take_slot(&self) -> bool {
787 let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner());
788 state.outstanding += 1;
789 let grow = state.alive < state.outstanding && state.alive < MAX_BLOCKING_WORKERS;
790 if grow {
791 state.alive += 1;
792 }
793 grow
794 }
795
796 fn release_slot(&self) {
797 let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner());
798 state.outstanding = state.outstanding.saturating_sub(1);
799 }
800
801 fn start_worker(&self) {
803 let receiver = Arc::clone(&self.receiver);
804 let counters = Arc::clone(&self.state);
805 let started = std::thread::Builder::new()
806 .name("cranpose-blocking".to_string())
807 .spawn(move || loop {
808 let job = {
809 let queue = receiver.lock().unwrap_or_else(|error| error.into_inner());
810 queue.recv()
811 };
812 let Ok(job) = job else {
813 break;
814 };
815 job();
816 let mut counters = counters.lock().unwrap_or_else(|error| error.into_inner());
817 counters.outstanding = counters.outstanding.saturating_sub(1);
818 });
819 if started.is_err() {
820 let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner());
821 state.alive -= 1;
822 }
823 }
824}
825
826#[cfg(not(target_arch = "wasm32"))]
827struct BlockingWork<T> {
828 slot: Arc<Mutex<Option<T>>>,
829 done: Arc<AtomicBool>,
830 wakers: Arc<Mutex<Vec<Waker>>>,
831}
832
833#[cfg(not(target_arch = "wasm32"))]
834impl<T> Future for BlockingWork<T> {
835 type Output = T;
836
837 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<T> {
838 if self.done.load(Ordering::Acquire) {
839 if let Some(value) = self
840 .slot
841 .lock()
842 .unwrap_or_else(|error| error.into_inner())
843 .take()
844 {
845 return Poll::Ready(value);
846 }
847 }
848 self.wakers
849 .lock()
850 .unwrap_or_else(|error| error.into_inner())
851 .push(context.waker().clone());
852 if self.done.load(Ordering::Acquire) {
854 if let Some(value) = self
855 .slot
856 .lock()
857 .unwrap_or_else(|error| error.into_inner())
858 .take()
859 {
860 return Poll::Ready(value);
861 }
862 }
863 Poll::Pending
864 }
865}
866
867#[allow(non_snake_case)]
875#[track_caller]
876pub fn produceState<T, K, F>(initial: T, key: K, producer: F) -> State<T>
877where
878 T: Clone + 'static,
879 K: PartialEq + 'static,
880 F: FnOnce(ProduceScope<T>) -> Pin<Box<dyn Future<Output = ()>>> + 'static,
881{
882 let state = remember(|| mutableStateOf(initial)).with(|state| *state);
883 let handle = ProduceScope { state };
884 crate::__launched_effect_async_impl(
885 crate::location_key(file!(), line!(), column!()),
886 std::panic::Location::caller().into(),
887 key,
888 move |_scope| producer(handle),
889 );
890 state.as_state()
891}
892
893pub struct ProduceScope<T: Clone + 'static> {
895 state: MutableState<T>,
896}
897
898impl<T: Clone + 'static> ProduceScope<T> {
899 pub fn set(&self, value: T) {
901 self.state.set(value);
902 }
903}
904
905#[cfg(test)]
906mod tests {
907 use super::*;
908
909 #[test]
910 fn a_delay_resolves_after_its_deadline() {
911 let started = Instant::now();
912 pollster::block_on(delay(Duration::from_millis(30)));
913 assert!(started.elapsed() >= Duration::from_millis(25));
914 }
915
916 #[test]
917 fn many_delays_share_one_timer_and_all_fire() {
918 let started = Instant::now();
919 pollster::block_on(async {
920 for _ in 0..4 {
921 delay(Duration::from_millis(5)).await;
922 }
923 });
924 assert!(started.elapsed() >= Duration::from_millis(15));
925 }
926
927 #[test]
928 fn an_elapsed_delay_is_ready_without_arming_the_timer() {
929 let mut future = Box::pin(Delay {
930 deadline: Instant::now() - Duration::from_millis(1),
931 armed: false,
932 fired: Arc::new(AtomicBool::new(false)),
933 });
934 let waker = Waker::noop().clone();
935 assert!(future
936 .as_mut()
937 .poll(&mut Context::from_waker(&waker))
938 .is_ready());
939 }
940}
941
942#[cfg(test)]
943mod stream_tests {
944 use super::*;
945
946 #[test]
947 fn a_channel_wakes_its_collector_and_ends_when_closed() {
948 let channel: EventChannel<u32> = EventChannel::new();
949 let stream = channel.stream();
950
951 let mut pending = Box::pin(stream.next());
952 let waker = Waker::noop().clone();
953 let mut context = Context::from_waker(&waker);
954 assert!(pending.as_mut().poll(&mut context).is_pending());
955
956 channel.send(7);
957 assert_eq!(pending.as_mut().poll(&mut context), Poll::Ready(Some(7)));
958
959 channel.send(8);
960 channel.close();
961 assert_eq!(pollster::block_on(stream.next()), Some(8));
963 assert_eq!(pollster::block_on(stream.next()), None);
964 assert_eq!(stream.delivered(), 2);
965 }
966
967 #[test]
968 fn an_event_goes_to_exactly_one_collector() {
969 let channel: EventChannel<u32> = EventChannel::new();
970 let first = channel.stream();
971 let second = first.clone();
972 channel.send(1);
973 channel.close();
974 assert_eq!(pollster::block_on(first.next()), Some(1));
975 assert_eq!(pollster::block_on(second.next()), None);
976 }
977
978 #[test]
979 fn sending_after_close_is_ignored() {
980 let channel: EventChannel<u32> = EventChannel::new();
981 let stream = channel.stream();
982 channel.close();
983 channel.send(1);
984 assert_eq!(pollster::block_on(stream.next()), None);
985 assert_eq!(channel.pending(), 0);
986 }
987
988 #[test]
989 fn blocking_work_resolves_with_its_result() {
990 let doubled = pollster::block_on(withBlocking(|| 21 * 2));
991 assert_eq!(doubled, 42);
992 }
993}
994
995#[cfg(test)]
996mod timer_race_tests {
997 use super::*;
998
999 #[test]
1003 fn concurrent_arming_never_loses_a_wake_up() {
1004 let rounds = 40;
1005 let threads: Vec<_> = (0..8)
1006 .map(|worker| {
1007 std::thread::spawn(move || {
1008 for round in 0..rounds {
1009 let millis = 1 + ((worker + round) % 5) as u64;
1010 pollster::block_on(delay(Duration::from_millis(millis)));
1011 }
1012 })
1013 })
1014 .collect();
1015 for thread in threads {
1016 thread.join().expect("every waiter is woken");
1017 }
1018 }
1019
1020 #[cfg(not(target_arch = "wasm32"))]
1021 #[test]
1022 fn blocking_work_reuses_its_threads_instead_of_one_per_call() {
1023 use std::{collections::HashSet, sync::mpsc};
1024
1025 let pool = BlockingPool::new();
1029
1030 let (sender, receiver) = mpsc::channel();
1034 for _ in 0..16 {
1035 let done = Arc::new((Mutex::new(false), Condvar::new()));
1036 let waiter = Arc::clone(&done);
1037 let sender = sender.clone();
1038 pool.submit(Box::new(move || {
1039 let _ = sender.send(std::thread::current().id());
1040 let (lock, signal) = &*done;
1041 *lock.lock().unwrap_or_else(|error| error.into_inner()) = true;
1042 signal.notify_all();
1043 }));
1044 let (lock, signal) = &*waiter;
1045 let mut finished = lock.lock().unwrap_or_else(|error| error.into_inner());
1046 while !*finished {
1047 finished = signal
1048 .wait(finished)
1049 .unwrap_or_else(|error| error.into_inner());
1050 }
1051 }
1052 drop(sender);
1053
1054 let threads: HashSet<_> = receiver.iter().collect();
1055 assert!(
1056 threads.len() < 16,
1057 "sixteen serial jobs used {} threads; the pool is not reusing them",
1058 threads.len()
1059 );
1060 }
1061
1062 #[cfg(not(target_arch = "wasm32"))]
1063 #[test]
1064 fn blocking_work_grows_so_one_slow_job_cannot_hold_up_another() {
1065 let pool = BlockingPool::new();
1068 let started = Arc::new((Mutex::new(0usize), Condvar::new()));
1069 let release = Arc::new((Mutex::new(false), Condvar::new()));
1070
1071 for _ in 0..4 {
1072 let started = Arc::clone(&started);
1073 let release = Arc::clone(&release);
1074 pool.submit(Box::new(move || {
1075 {
1076 let (count, signal) = &*started;
1077 *count.lock().unwrap_or_else(|error| error.into_inner()) += 1;
1078 signal.notify_all();
1079 }
1080 let (held, signal) = &*release;
1081 let mut go = held.lock().unwrap_or_else(|error| error.into_inner());
1082 while !*go {
1083 go = signal.wait(go).unwrap_or_else(|error| error.into_inner());
1084 }
1085 }));
1086 }
1087
1088 let (count, signal) = &*started;
1091 let mut running = count.lock().unwrap_or_else(|error| error.into_inner());
1092 while *running < 4 {
1093 running = signal
1094 .wait(running)
1095 .unwrap_or_else(|error| error.into_inner());
1096 }
1097 drop(running);
1098
1099 let (held, signal) = &*release;
1100 *held.lock().unwrap_or_else(|error| error.into_inner()) = true;
1101 signal.notify_all();
1102 }
1103}