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
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 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#[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
111pub 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
124pub 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
151pub 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
294pub 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 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 pub fn stream(&self) -> EventStream<T> {
340 EventStream {
341 shared: Rc::clone(&self.shared),
342 }
343 }
344
345 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 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 pub fn is_closed(&self) -> bool {
365 self.shared.closed.get()
366 }
367
368 pub fn pending(&self) -> usize {
370 self.shared.ready.borrow().len()
371 }
372}
373
374pub 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 pub fn next(&self) -> EventStreamNext<T> {
395 EventStreamNext {
396 shared: Rc::clone(&self.shared),
397 }
398 }
399
400 pub fn delivered(&self) -> usize {
402 self.shared.delivered.get()
403 }
404}
405
406pub 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#[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#[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
474pub 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 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#[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#[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#[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#[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
828pub struct ProduceScope<T: Clone + 'static> {
830 state: MutableState<T>,
831}
832
833impl<T: Clone + 'static> ProduceScope<T> {
834 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}