1use crate::hooks::{mutableStateOf, remember};
12use crate::runtime::{current_runtime_handle, RuntimeHandle, TaskHandle};
13use crate::state::{MutableState, State};
14use std::cell::RefCell;
15use std::future::Future;
16use std::pin::Pin;
17use std::rc::Rc;
18use std::sync::atomic::{AtomicBool, Ordering};
19use std::sync::{Arc, OnceLock};
20#[cfg(not(target_arch = "wasm32"))]
21use std::sync::{Condvar, Mutex};
22use std::task::{Context, Poll, Waker};
23use std::time::Duration;
24#[cfg(target_arch = "wasm32")]
25use wasm_bindgen::JsCast;
26use web_time::Instant;
27
28pub fn spawn_ui_task(future: impl Future<Output = ()> + 'static) -> Option<TaskHandle> {
34 current_runtime_handle().and_then(|runtime| runtime.spawn_ui(future))
35}
36
37#[derive(Clone)]
43pub struct CoroutineScope {
44 inner: Rc<ScopeInner>,
45}
46
47struct ScopeInner {
48 runtime: Option<RuntimeHandle>,
49 tasks: RefCell<Vec<TaskHandle>>,
50}
51
52impl Drop for ScopeInner {
53 fn drop(&mut self) {
54 for task in self.tasks.borrow_mut().drain(..) {
55 task.cancel();
56 }
57 }
58}
59
60impl CoroutineScope {
61 pub fn launch(&self, future: impl Future<Output = ()> + 'static) {
64 let Some(runtime) = self.inner.runtime.clone() else {
65 log::warn!("cranpose: a coroutine scope with no runtime dropped its work");
66 return;
67 };
68 self.inner
71 .tasks
72 .borrow_mut()
73 .retain(|task| !task.is_finished());
74 if let Some(handle) = runtime.spawn_ui(future) {
75 self.inner.tasks.borrow_mut().push(handle);
76 }
77 }
78
79 pub fn cancel(&self) {
81 for task in self.inner.tasks.borrow_mut().drain(..) {
82 task.cancel();
83 }
84 }
85}
86
87#[allow(non_snake_case)]
89pub fn rememberCoroutineScope() -> CoroutineScope {
90 remember(|| CoroutineScope {
91 inner: Rc::new(ScopeInner {
92 runtime: current_runtime_handle(),
93 tasks: RefCell::new(Vec::new()),
94 }),
95 })
96 .with(|scope| scope.clone())
97}
98
99pub fn delay(duration: Duration) -> Delay {
107 Delay {
108 deadline: Instant::now() + duration,
109 armed: false,
110 fired: Arc::new(AtomicBool::new(false)),
111 }
112}
113
114pub struct Delay {
116 deadline: Instant,
117 armed: bool,
118 fired: Arc<AtomicBool>,
119}
120
121impl Future for Delay {
122 type Output = ();
123
124 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<()> {
125 if self.fired.load(Ordering::Acquire) || Instant::now() >= self.deadline {
126 return Poll::Ready(());
127 }
128 let this = self.get_mut();
129 if !this.armed {
130 this.armed = true;
131 timer().arm(
132 this.deadline,
133 context.waker().clone(),
134 Arc::clone(&this.fired),
135 );
136 }
137 Poll::Pending
138 }
139}
140
141pub async fn interval(period: Duration, mut tick: impl FnMut()) {
146 loop {
147 delay(period).await;
148 tick();
149 }
150}
151
152#[cfg(not(target_arch = "wasm32"))]
154struct Alarm {
155 deadline: Instant,
156 waker: Waker,
157 fired: Arc<AtomicBool>,
158}
159
160struct Timer {
167 #[cfg(not(target_arch = "wasm32"))]
168 alarms: Mutex<Vec<Alarm>>,
169 #[cfg(not(target_arch = "wasm32"))]
170 wake: Condvar,
171}
172
173fn timer() -> &'static Timer {
174 static TIMER: OnceLock<&'static Timer> = OnceLock::new();
175 TIMER.get_or_init(|| {
176 let timer: &'static Timer = Box::leak(Box::new(Timer::new()));
177 timer.start();
178 timer
179 })
180}
181
182#[cfg(not(target_arch = "wasm32"))]
183impl Timer {
184 fn new() -> Self {
185 Self {
186 alarms: Mutex::new(Vec::new()),
187 wake: Condvar::new(),
188 }
189 }
190
191 fn start(&'static self) {
192 std::thread::Builder::new()
193 .name("cranpose-timer".to_string())
194 .spawn(move || self.run())
195 .expect("the timer thread starts");
196 }
197
198 fn run(&self) {
207 let mut alarms = self
208 .alarms
209 .lock()
210 .unwrap_or_else(|error| error.into_inner());
211 loop {
212 let now = Instant::now();
213 let mut due = Vec::new();
214 let mut next: Option<Duration> = None;
215 alarms.retain(|alarm| {
216 if alarm.deadline <= now {
217 due.push((alarm.waker.clone(), Arc::clone(&alarm.fired)));
218 false
219 } else {
220 let remaining = alarm.deadline - now;
221 next = Some(next.map_or(remaining, |current| current.min(remaining)));
222 true
223 }
224 });
225
226 if !due.is_empty() {
227 drop(alarms);
230 for (waker, fired) in due {
231 fired.store(true, Ordering::Release);
232 waker.wake();
233 }
234 alarms = self
235 .alarms
236 .lock()
237 .unwrap_or_else(|error| error.into_inner());
238 continue;
239 }
240
241 alarms = match next {
242 Some(timeout) => {
243 self.wake
244 .wait_timeout(alarms, timeout)
245 .unwrap_or_else(|error| error.into_inner())
246 .0
247 }
248 None => self
249 .wake
250 .wait(alarms)
251 .unwrap_or_else(|error| error.into_inner()),
252 };
253 }
254 }
255
256 fn arm(&self, deadline: Instant, waker: Waker, fired: Arc<AtomicBool>) {
257 let mut alarms = self
258 .alarms
259 .lock()
260 .unwrap_or_else(|error| error.into_inner());
261 alarms.push(Alarm {
262 deadline,
263 waker,
264 fired,
265 });
266 self.wake.notify_one();
267 }
268}
269
270#[cfg(target_arch = "wasm32")]
271impl Timer {
272 fn new() -> Self {
273 Self {}
274 }
275
276 fn start(&'static self) {}
277
278 fn arm(&self, deadline: Instant, waker: Waker, fired: Arc<AtomicBool>) {
279 let millis = deadline
280 .saturating_duration_since(Instant::now())
281 .as_millis()
282 .min(i32::MAX as u128) as i32;
283 let callback = wasm_bindgen::closure::Closure::once_into_js(move || {
284 fired.store(true, Ordering::Release);
285 waker.wake();
286 });
287 let scheduled = web_sys::window().and_then(|window| {
288 window
289 .set_timeout_with_callback_and_timeout_and_arguments_0(
290 callback.unchecked_ref(),
291 millis,
292 )
293 .ok()
294 });
295 if scheduled.is_none() {
296 log::warn!("cranpose: no window timer is available; the delay resolves immediately");
297 }
298 }
299}
300
301pub struct EventChannel<T: 'static> {
310 shared: Rc<ChannelShared<T>>,
311}
312
313struct ChannelShared<T: 'static> {
314 ready: RefCell<std::collections::VecDeque<T>>,
315 closed: std::cell::Cell<bool>,
316 delivered: std::cell::Cell<usize>,
317 wakers: RefCell<Vec<Waker>>,
318}
319
320impl<T: 'static> ChannelShared<T> {
321 fn wake_all(&self) {
322 for waker in self.wakers.borrow_mut().drain(..) {
323 waker.wake();
324 }
325 }
326}
327
328impl<T: 'static> Default for EventChannel<T> {
329 fn default() -> Self {
330 Self::new()
331 }
332}
333
334impl<T: 'static> EventChannel<T> {
335 pub fn new() -> Self {
337 Self {
338 shared: Rc::new(ChannelShared {
339 ready: RefCell::new(std::collections::VecDeque::new()),
340 closed: std::cell::Cell::new(false),
341 delivered: std::cell::Cell::new(0),
342 wakers: RefCell::new(Vec::new()),
343 }),
344 }
345 }
346
347 pub fn stream(&self) -> EventStream<T> {
349 EventStream {
350 shared: Rc::clone(&self.shared),
351 }
352 }
353
354 pub fn send(&self, event: T) {
356 if self.shared.closed.get() {
357 return;
358 }
359 self.shared.ready.borrow_mut().push_back(event);
360 self.shared.wake_all();
361 }
362
363 pub fn close(&self) {
365 if self.shared.closed.get() {
366 return;
367 }
368 self.shared.closed.set(true);
369 self.shared.wake_all();
370 }
371
372 pub fn is_closed(&self) -> bool {
374 self.shared.closed.get()
375 }
376
377 pub fn pending(&self) -> usize {
379 self.shared.ready.borrow().len()
380 }
381}
382
383pub struct EventStream<T: 'static> {
389 shared: Rc<ChannelShared<T>>,
390}
391
392impl<T: 'static> Clone for EventStream<T> {
393 fn clone(&self) -> Self {
394 Self {
395 shared: Rc::clone(&self.shared),
396 }
397 }
398}
399
400impl<T: 'static> EventStream<T> {
401 pub fn next(&self) -> EventStreamNext<T> {
404 EventStreamNext {
405 shared: Rc::clone(&self.shared),
406 }
407 }
408
409 pub fn delivered(&self) -> usize {
411 self.shared.delivered.get()
412 }
413}
414
415pub struct EventStreamNext<T: 'static> {
417 shared: Rc<ChannelShared<T>>,
418}
419
420impl<T: 'static> Future for EventStreamNext<T> {
421 type Output = Option<T>;
422
423 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<T>> {
424 if let Some(event) = self.shared.ready.borrow_mut().pop_front() {
425 self.shared.delivered.set(self.shared.delivered.get() + 1);
426 return Poll::Ready(Some(event));
427 }
428 if self.shared.closed.get() {
429 return Poll::Ready(None);
430 }
431 self.shared
432 .wakers
433 .borrow_mut()
434 .push(context.waker().clone());
435 Poll::Pending
436 }
437}
438
439#[allow(non_snake_case)]
445#[track_caller]
446pub fn CollectEvents<T, K>(stream: EventStream<T>, key: K, on_event: impl FnMut(T) + 'static)
447where
448 T: 'static,
449 K: std::hash::Hash + 'static,
450{
451 crate::__launched_effect_async_impl(
452 crate::location_key(file!(), line!(), column!()),
453 std::panic::Location::caller().into(),
454 key,
455 move |_scope| {
456 let mut on_event = on_event;
457 Box::pin(async move {
458 while let Some(event) = stream.next().await {
459 on_event(event);
460 }
461 })
462 },
463 );
464}
465
466#[allow(non_snake_case)]
471#[track_caller]
472pub fn collectAsState<T, K>(stream: EventStream<T>, key: K, initial: T) -> State<T>
473where
474 T: Clone + 'static,
475 K: std::hash::Hash + 'static,
476{
477 let state = remember(|| mutableStateOf(initial)).with(|state| *state);
478 let sink = state;
479 CollectEvents(stream, key, move |event| sink.set(event));
480 state.as_state()
481}
482
483pub struct EventSender<T: Send + 'static> {
493 #[cfg(not(target_arch = "wasm32"))]
496 dispatcher: crate::runtime::UiDispatcher,
497 bridge: u64,
498 _events: std::marker::PhantomData<fn(T)>,
499}
500
501impl<T: Send + 'static> Clone for EventSender<T> {
502 fn clone(&self) -> Self {
503 Self {
504 #[cfg(not(target_arch = "wasm32"))]
505 dispatcher: self.dispatcher.clone(),
506 bridge: self.bridge,
507 _events: std::marker::PhantomData,
508 }
509 }
510}
511
512impl<T: Send + 'static> EventSender<T> {
513 pub fn send(&self, event: T) {
515 let bridge = self.bridge;
516 #[cfg(not(target_arch = "wasm32"))]
517 self.dispatcher
518 .post(move || deliver_bridged::<T>(bridge, event));
519 #[cfg(target_arch = "wasm32")]
520 deliver_bridged::<T>(bridge, event);
521 }
522}
523
524thread_local! {
525 static BRIDGES: RefCell<std::collections::HashMap<u64, Rc<dyn std::any::Any>>> =
528 RefCell::new(std::collections::HashMap::new());
529}
530
531static NEXT_BRIDGE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
532
533fn deliver_bridged<T: Send + 'static>(bridge: u64, event: T) {
534 let channel = BRIDGES.with(|bridges| bridges.borrow().get(&bridge).cloned());
535 let Some(channel) = channel else {
536 return;
539 };
540 if let Ok(channel) = channel.downcast::<EventChannel<T>>() {
541 channel.send(event);
542 }
543}
544
545struct Bridge<T: Send + 'static> {
548 id: u64,
549 channel: Rc<EventChannel<T>>,
550}
551
552impl<T: Send + 'static> Bridge<T> {
553 fn new() -> Self {
554 let id = NEXT_BRIDGE.fetch_add(1, Ordering::Relaxed);
555 let channel = Rc::new(EventChannel::<T>::new());
556 BRIDGES.with(|bridges| {
557 bridges
558 .borrow_mut()
559 .insert(id, Rc::clone(&channel) as Rc<dyn std::any::Any>)
560 });
561 Self { id, channel }
562 }
563}
564
565impl<T: Send + 'static> Drop for Bridge<T> {
566 fn drop(&mut self) {
567 BRIDGES.with(|bridges| bridges.borrow_mut().remove(&self.id));
568 self.channel.close();
569 }
570}
571
572#[allow(non_snake_case)]
580pub fn rememberEventStream<T, K, R, S>(key: K, subscribe: S) -> EventStream<T>
581where
582 T: Send + 'static,
583 K: std::hash::Hash + 'static,
584 R: 'static,
585 S: FnOnce(EventSender<T>) -> R + 'static,
586{
587 let bridge = remember(Bridge::<T>::new);
588 let (id, stream) = bridge.with(|bridge| (bridge.id, bridge.channel.stream()));
589 #[cfg(not(target_arch = "wasm32"))]
590 let dispatcher = current_runtime_handle().map(|runtime| runtime.dispatcher());
591
592 crate::__disposable_effect_impl(
593 crate::location_key(file!(), line!(), column!()),
594 key,
595 move |scope| {
596 #[cfg(not(target_arch = "wasm32"))]
597 let Some(dispatcher) = dispatcher
598 else {
599 log::warn!("cranpose: an event stream was remembered without a runtime");
600 return scope.on_dispose(|| {});
601 };
602 let registration = subscribe(EventSender {
603 #[cfg(not(target_arch = "wasm32"))]
604 dispatcher,
605 bridge: id,
606 _events: std::marker::PhantomData,
607 });
608 scope.on_dispose(move || drop(registration))
609 },
610 );
611
612 stream
613}
614
615#[allow(non_snake_case)]
624pub async fn withBlocking<T, F>(work: F) -> T
625where
626 T: Send + 'static,
627 F: FnOnce() -> T + Send + 'static,
628{
629 #[cfg(not(target_arch = "wasm32"))]
630 {
631 let slot: Arc<Mutex<Option<T>>> = Arc::new(Mutex::new(None));
632 let done = Arc::new(AtomicBool::new(false));
633 let wakers: Arc<Mutex<Vec<Waker>>> = Arc::new(Mutex::new(Vec::new()));
634
635 let worker_slot = Arc::clone(&slot);
636 let worker_done = Arc::clone(&done);
637 let worker_wakers = Arc::clone(&wakers);
638 BlockingPool::get().submit(Box::new(move || {
639 let value = work();
640 *worker_slot
641 .lock()
642 .unwrap_or_else(|error| error.into_inner()) = Some(value);
643 worker_done.store(true, Ordering::Release);
644 for waker in worker_wakers
645 .lock()
646 .unwrap_or_else(|error| error.into_inner())
647 .drain(..)
648 {
649 waker.wake();
650 }
651 }));
652
653 BlockingWork { slot, done, wakers }.await
654 }
655 #[cfg(target_arch = "wasm32")]
656 {
657 work()
658 }
659}
660
661#[allow(non_snake_case)]
680pub fn launchBlocking<T>(work: impl FnOnce() -> T + Send + 'static, on_ui: impl FnOnce(T) + 'static)
681where
682 T: Send + 'static,
683{
684 let Some(runtime) = current_runtime_handle() else {
685 on_ui(work());
686 return;
687 };
688 let Some(continuation) = runtime.register_ui_cont(on_ui) else {
689 return;
690 };
691 let dispatcher = runtime.dispatcher();
692 #[cfg(not(target_arch = "wasm32"))]
693 BlockingPool::get().submit(Box::new(move || {
694 dispatcher.post_invoke(continuation, work());
695 }));
696 #[cfg(target_arch = "wasm32")]
697 dispatcher.post_invoke(continuation, work());
698}
699
700#[cfg(not(target_arch = "wasm32"))]
713struct BlockingPool {
714 sender: std::sync::mpsc::Sender<BlockingJob>,
715 receiver: Arc<Mutex<std::sync::mpsc::Receiver<BlockingJob>>>,
716 state: Arc<Mutex<PoolState>>,
717}
718
719#[cfg(not(target_arch = "wasm32"))]
727#[derive(Clone, Copy, Default)]
728struct PoolState {
729 alive: usize,
730 outstanding: usize,
731}
732
733#[cfg(not(target_arch = "wasm32"))]
734type BlockingJob = Box<dyn FnOnce() + Send + 'static>;
735
736#[cfg(not(target_arch = "wasm32"))]
740const MAX_BLOCKING_WORKERS: usize = 64;
741
742#[cfg(not(target_arch = "wasm32"))]
746const _: () = assert!(MAX_BLOCKING_WORKERS > 0 && MAX_BLOCKING_WORKERS <= 256);
747
748#[cfg(not(target_arch = "wasm32"))]
749impl BlockingPool {
750 fn get() -> &'static BlockingPool {
751 static POOL: OnceLock<BlockingPool> = OnceLock::new();
752 POOL.get_or_init(BlockingPool::new)
753 }
754
755 fn new() -> BlockingPool {
756 let (sender, receiver) = std::sync::mpsc::channel();
757 BlockingPool {
758 sender,
759 receiver: Arc::new(Mutex::new(receiver)),
760 state: Arc::new(Mutex::new(PoolState::default())),
761 }
762 }
763
764 fn submit(&self, job: BlockingJob) {
765 if self.take_slot() {
766 self.start_worker();
767 }
768 if let Err(returned) = self.sender.send(job) {
771 self.release_slot();
772 (returned.0)();
773 }
774 }
775
776 fn take_slot(&self) -> bool {
779 let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner());
780 state.outstanding += 1;
781 let grow = state.alive < state.outstanding && state.alive < MAX_BLOCKING_WORKERS;
782 if grow {
783 state.alive += 1;
784 }
785 grow
786 }
787
788 fn release_slot(&self) {
789 let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner());
790 state.outstanding = state.outstanding.saturating_sub(1);
791 }
792
793 fn start_worker(&self) {
795 let receiver = Arc::clone(&self.receiver);
796 let counters = Arc::clone(&self.state);
797 let started = std::thread::Builder::new()
798 .name("cranpose-blocking".to_string())
799 .spawn(move || loop {
800 let job = {
801 let queue = receiver.lock().unwrap_or_else(|error| error.into_inner());
802 queue.recv()
803 };
804 let Ok(job) = job else {
805 break;
806 };
807 job();
808 let mut counters = counters.lock().unwrap_or_else(|error| error.into_inner());
809 counters.outstanding = counters.outstanding.saturating_sub(1);
810 });
811 if started.is_err() {
812 let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner());
813 state.alive -= 1;
814 }
815 }
816}
817
818#[cfg(not(target_arch = "wasm32"))]
819struct BlockingWork<T> {
820 slot: Arc<Mutex<Option<T>>>,
821 done: Arc<AtomicBool>,
822 wakers: Arc<Mutex<Vec<Waker>>>,
823}
824
825#[cfg(not(target_arch = "wasm32"))]
826impl<T> Future for BlockingWork<T> {
827 type Output = T;
828
829 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<T> {
830 if self.done.load(Ordering::Acquire) {
831 if let Some(value) = self
832 .slot
833 .lock()
834 .unwrap_or_else(|error| error.into_inner())
835 .take()
836 {
837 return Poll::Ready(value);
838 }
839 }
840 self.wakers
841 .lock()
842 .unwrap_or_else(|error| error.into_inner())
843 .push(context.waker().clone());
844 if self.done.load(Ordering::Acquire) {
846 if let Some(value) = self
847 .slot
848 .lock()
849 .unwrap_or_else(|error| error.into_inner())
850 .take()
851 {
852 return Poll::Ready(value);
853 }
854 }
855 Poll::Pending
856 }
857}
858
859#[allow(non_snake_case)]
867#[track_caller]
868pub fn produceState<T, K, F>(initial: T, key: K, producer: F) -> State<T>
869where
870 T: Clone + 'static,
871 K: std::hash::Hash + 'static,
872 F: FnOnce(ProduceScope<T>) -> Pin<Box<dyn Future<Output = ()>>> + 'static,
873{
874 let state = remember(|| mutableStateOf(initial)).with(|state| *state);
875 let handle = ProduceScope { state };
876 crate::__launched_effect_async_impl(
877 crate::location_key(file!(), line!(), column!()),
878 std::panic::Location::caller().into(),
879 key,
880 move |_scope| producer(handle),
881 );
882 state.as_state()
883}
884
885pub struct ProduceScope<T: Clone + 'static> {
887 state: MutableState<T>,
888}
889
890impl<T: Clone + 'static> ProduceScope<T> {
891 pub fn set(&self, value: T) {
893 self.state.set(value);
894 }
895}
896
897#[cfg(test)]
898mod tests {
899 use super::*;
900
901 #[test]
902 fn a_delay_resolves_after_its_deadline() {
903 let started = Instant::now();
904 pollster::block_on(delay(Duration::from_millis(30)));
905 assert!(started.elapsed() >= Duration::from_millis(25));
906 }
907
908 #[test]
909 fn many_delays_share_one_timer_and_all_fire() {
910 let started = Instant::now();
911 pollster::block_on(async {
912 for _ in 0..4 {
913 delay(Duration::from_millis(5)).await;
914 }
915 });
916 assert!(started.elapsed() >= Duration::from_millis(15));
917 }
918
919 #[test]
920 fn an_elapsed_delay_is_ready_without_arming_the_timer() {
921 let mut future = Box::pin(Delay {
922 deadline: Instant::now() - Duration::from_millis(1),
923 armed: false,
924 fired: Arc::new(AtomicBool::new(false)),
925 });
926 let waker = Waker::noop().clone();
927 assert!(future
928 .as_mut()
929 .poll(&mut Context::from_waker(&waker))
930 .is_ready());
931 }
932}
933
934#[cfg(test)]
935mod stream_tests {
936 use super::*;
937
938 #[test]
939 fn a_channel_wakes_its_collector_and_ends_when_closed() {
940 let channel: EventChannel<u32> = EventChannel::new();
941 let stream = channel.stream();
942
943 let mut pending = Box::pin(stream.next());
944 let waker = Waker::noop().clone();
945 let mut context = Context::from_waker(&waker);
946 assert!(pending.as_mut().poll(&mut context).is_pending());
947
948 channel.send(7);
949 assert_eq!(pending.as_mut().poll(&mut context), Poll::Ready(Some(7)));
950
951 channel.send(8);
952 channel.close();
953 assert_eq!(pollster::block_on(stream.next()), Some(8));
955 assert_eq!(pollster::block_on(stream.next()), None);
956 assert_eq!(stream.delivered(), 2);
957 }
958
959 #[test]
960 fn an_event_goes_to_exactly_one_collector() {
961 let channel: EventChannel<u32> = EventChannel::new();
962 let first = channel.stream();
963 let second = first.clone();
964 channel.send(1);
965 channel.close();
966 assert_eq!(pollster::block_on(first.next()), Some(1));
967 assert_eq!(pollster::block_on(second.next()), None);
968 }
969
970 #[test]
971 fn sending_after_close_is_ignored() {
972 let channel: EventChannel<u32> = EventChannel::new();
973 let stream = channel.stream();
974 channel.close();
975 channel.send(1);
976 assert_eq!(pollster::block_on(stream.next()), None);
977 assert_eq!(channel.pending(), 0);
978 }
979
980 #[test]
981 fn blocking_work_resolves_with_its_result() {
982 let doubled = pollster::block_on(withBlocking(|| 21 * 2));
983 assert_eq!(doubled, 42);
984 }
985}
986
987#[cfg(test)]
988mod timer_race_tests {
989 use super::*;
990
991 #[test]
995 fn concurrent_arming_never_loses_a_wake_up() {
996 let rounds = 40;
997 let threads: Vec<_> = (0..8)
998 .map(|worker| {
999 std::thread::spawn(move || {
1000 for round in 0..rounds {
1001 let millis = 1 + ((worker + round) % 5) as u64;
1002 pollster::block_on(delay(Duration::from_millis(millis)));
1003 }
1004 })
1005 })
1006 .collect();
1007 for thread in threads {
1008 thread.join().expect("every waiter is woken");
1009 }
1010 }
1011
1012 #[cfg(not(target_arch = "wasm32"))]
1013 #[test]
1014 fn blocking_work_reuses_its_threads_instead_of_one_per_call() {
1015 use std::collections::HashSet;
1016 use std::sync::mpsc;
1017
1018 let pool = BlockingPool::new();
1022
1023 let (sender, receiver) = mpsc::channel();
1027 for _ in 0..16 {
1028 let done = Arc::new((Mutex::new(false), Condvar::new()));
1029 let waiter = Arc::clone(&done);
1030 let sender = sender.clone();
1031 pool.submit(Box::new(move || {
1032 let _ = sender.send(std::thread::current().id());
1033 let (lock, signal) = &*done;
1034 *lock.lock().unwrap_or_else(|error| error.into_inner()) = true;
1035 signal.notify_all();
1036 }));
1037 let (lock, signal) = &*waiter;
1038 let mut finished = lock.lock().unwrap_or_else(|error| error.into_inner());
1039 while !*finished {
1040 finished = signal
1041 .wait(finished)
1042 .unwrap_or_else(|error| error.into_inner());
1043 }
1044 }
1045 drop(sender);
1046
1047 let threads: HashSet<_> = receiver.iter().collect();
1048 assert!(
1049 threads.len() < 16,
1050 "sixteen serial jobs used {} threads; the pool is not reusing them",
1051 threads.len()
1052 );
1053 }
1054
1055 #[cfg(not(target_arch = "wasm32"))]
1056 #[test]
1057 fn blocking_work_grows_so_one_slow_job_cannot_hold_up_another() {
1058 let pool = BlockingPool::new();
1061 let started = Arc::new((Mutex::new(0usize), Condvar::new()));
1062 let release = Arc::new((Mutex::new(false), Condvar::new()));
1063
1064 for _ in 0..4 {
1065 let started = Arc::clone(&started);
1066 let release = Arc::clone(&release);
1067 pool.submit(Box::new(move || {
1068 {
1069 let (count, signal) = &*started;
1070 *count.lock().unwrap_or_else(|error| error.into_inner()) += 1;
1071 signal.notify_all();
1072 }
1073 let (held, signal) = &*release;
1074 let mut go = held.lock().unwrap_or_else(|error| error.into_inner());
1075 while !*go {
1076 go = signal.wait(go).unwrap_or_else(|error| error.into_inner());
1077 }
1078 }));
1079 }
1080
1081 let (count, signal) = &*started;
1084 let mut running = count.lock().unwrap_or_else(|error| error.into_inner());
1085 while *running < 4 {
1086 running = signal
1087 .wait(running)
1088 .unwrap_or_else(|error| error.into_inner());
1089 }
1090 drop(running);
1091
1092 let (held, signal) = &*release;
1093 *held.lock().unwrap_or_else(|error| error.into_inner()) = true;
1094 signal.notify_all();
1095 }
1096}