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