1#[cfg(not(target_arch = "wasm32"))]
12use std::sync::{Condvar, Mutex};
13use std::{
14 cell::{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::{mutableStateOfNeverEqual, 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 closed: Cell<bool>,
59}
60
61impl Drop for ScopeInner {
62 fn drop(&mut self) {
63 for task in self.tasks.get_mut().drain(..) {
64 task.cancel();
65 }
66 }
67}
68
69struct CompositionScopeOwner(CoroutineScope);
70
71impl Drop for CompositionScopeOwner {
72 fn drop(&mut self) {
73 self.0.inner.closed.set(true);
74 self.0.cancel();
75 }
76}
77
78impl CoroutineScope {
79 pub fn launch(&self, future: impl Future<Output = ()> + 'static) {
82 if self.inner.closed.get() {
83 return;
84 }
85 let Some(runtime) = self.inner.runtime.clone() else {
86 log::warn!("cranpose: a coroutine scope with no runtime dropped its work");
87 return;
88 };
89 self.inner
90 .tasks
91 .borrow_mut()
92 .retain(|task| !task.is_finished());
93 if let Some(handle) = runtime.spawn_ui(future) {
94 self.inner.tasks.borrow_mut().push(handle);
95 }
96 }
97
98 pub fn cancel(&self) {
100 let tasks = std::mem::take(&mut *self.inner.tasks.borrow_mut());
101 for task in tasks {
102 task.cancel();
103 }
104 }
105
106 #[cfg(test)]
107 pub(crate) fn probe_identity(&self) -> usize {
108 Rc::as_ptr(&self.inner) as *const () as usize
109 }
110}
111
112#[allow(non_snake_case)]
114#[track_caller]
115pub fn rememberCoroutineScope() -> CoroutineScope {
116 remember(|| {
117 CompositionScopeOwner(CoroutineScope {
118 inner: Rc::new(ScopeInner {
119 runtime: current_runtime_handle(),
120 tasks: RefCell::new(Vec::new()),
121 closed: Cell::new(false),
122 }),
123 })
124 })
125 .with(|owner| owner.0.clone())
126}
127
128pub fn delay(duration: Duration) -> Delay {
134 Delay {
135 deadline: Instant::now() + duration,
136 armed: false,
137 fired: Arc::new(AtomicBool::new(false)),
138 }
139}
140
141pub struct Delay {
143 deadline: Instant,
144 armed: bool,
145 fired: Arc<AtomicBool>,
146}
147
148impl Future for Delay {
149 type Output = ();
150
151 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<()> {
152 if self.fired.load(Ordering::Acquire) || Instant::now() >= self.deadline {
153 return Poll::Ready(());
154 }
155 let this = self.get_mut();
156 if !this.armed {
157 this.armed = true;
158 timer().arm(
159 this.deadline,
160 context.waker().clone(),
161 Arc::clone(&this.fired),
162 );
163 }
164 Poll::Pending
165 }
166}
167
168pub async fn interval(period: Duration, mut tick: impl FnMut()) {
173 loop {
174 delay(period).await;
175 tick();
176 }
177}
178
179#[cfg(not(target_arch = "wasm32"))]
180struct Alarm {
181 deadline: Instant,
182 waker: Waker,
183 fired: Arc<AtomicBool>,
184}
185
186struct Timer {
187 #[cfg(not(target_arch = "wasm32"))]
188 alarms: Mutex<Vec<Alarm>>,
189 #[cfg(not(target_arch = "wasm32"))]
190 wake: Condvar,
191}
192
193fn timer() -> &'static Timer {
194 static TIMER: OnceLock<&'static Timer> = OnceLock::new();
195 TIMER.get_or_init(|| {
196 let timer: &'static Timer = Box::leak(Box::new(Timer::new()));
197 timer.start();
198 timer
199 })
200}
201
202#[cfg(not(target_arch = "wasm32"))]
203impl Timer {
204 fn new() -> Self {
205 Self {
206 alarms: Mutex::new(Vec::new()),
207 wake: Condvar::new(),
208 }
209 }
210
211 fn start(&'static self) {
212 std::thread::Builder::new()
213 .name("cranpose-timer".to_string())
214 .spawn(move || self.run())
215 .expect("the timer thread starts");
216 }
217
218 fn run(&self) {
219 let mut alarms = self
220 .alarms
221 .lock()
222 .unwrap_or_else(|error| error.into_inner());
223 loop {
224 let now = Instant::now();
225 let mut due = Vec::new();
226 let mut next: Option<Duration> = None;
227 alarms.retain(|alarm| {
228 if alarm.deadline <= now {
229 due.push((alarm.waker.clone(), Arc::clone(&alarm.fired)));
230 false
231 } else {
232 let remaining = alarm.deadline - now;
233 next = Some(next.map_or(remaining, |current| current.min(remaining)));
234 true
235 }
236 });
237
238 if !due.is_empty() {
239 drop(alarms);
240 for (waker, fired) in due {
241 fired.store(true, Ordering::Release);
242 waker.wake();
243 }
244 alarms = self
245 .alarms
246 .lock()
247 .unwrap_or_else(|error| error.into_inner());
248 continue;
249 }
250
251 alarms = match next {
252 Some(timeout) => {
253 self.wake
254 .wait_timeout(alarms, timeout)
255 .unwrap_or_else(|error| error.into_inner())
256 .0
257 }
258 None => self
259 .wake
260 .wait(alarms)
261 .unwrap_or_else(|error| error.into_inner()),
262 };
263 }
264 }
265
266 fn arm(&self, deadline: Instant, waker: Waker, fired: Arc<AtomicBool>) {
267 let mut alarms = self
268 .alarms
269 .lock()
270 .unwrap_or_else(|error| error.into_inner());
271 alarms.push(Alarm {
272 deadline,
273 waker,
274 fired,
275 });
276 self.wake.notify_one();
277 }
278}
279
280#[cfg(target_arch = "wasm32")]
281impl Timer {
282 fn new() -> Self {
283 Self {}
284 }
285
286 fn start(&'static self) {}
287
288 fn arm(&self, deadline: Instant, waker: Waker, fired: Arc<AtomicBool>) {
289 let millis = deadline
290 .saturating_duration_since(Instant::now())
291 .as_millis()
292 .min(i32::MAX as u128) as i32;
293 let callback = wasm_bindgen::closure::Closure::once_into_js(move || {
294 fired.store(true, Ordering::Release);
295 waker.wake();
296 });
297 let scheduled = web_sys::window().and_then(|window| {
298 window
299 .set_timeout_with_callback_and_timeout_and_arguments_0(
300 callback.unchecked_ref(),
301 millis,
302 )
303 .ok()
304 });
305 if scheduled.is_none() {
306 log::warn!("cranpose: no window timer is available; the delay resolves immediately");
307 }
308 }
309}
310
311pub 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::caller_location_key(),
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(|| mutableStateOfNeverEqual(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> {
499 #[cfg(not(target_arch = "wasm32"))]
500 dispatcher: crate::runtime::UiDispatcher,
501 bridge: u64,
502 _events: std::marker::PhantomData<fn(T)>,
503}
504
505impl<T: Send + 'static> Clone for EventSender<T> {
506 fn clone(&self) -> Self {
507 Self {
508 #[cfg(not(target_arch = "wasm32"))]
509 dispatcher: self.dispatcher.clone(),
510 bridge: self.bridge,
511 _events: std::marker::PhantomData,
512 }
513 }
514}
515
516impl<T: Send + 'static> EventSender<T> {
517 pub fn send(&self, event: T) {
519 let bridge = self.bridge;
520 #[cfg(not(target_arch = "wasm32"))]
521 self.dispatcher
522 .post(move || deliver_bridged::<T>(bridge, event));
523 #[cfg(target_arch = "wasm32")]
524 deliver_bridged::<T>(bridge, event);
525 }
526}
527
528thread_local! {
529 static BRIDGES: RefCell<std::collections::HashMap<u64, Rc<dyn std::any::Any>>> =
530 RefCell::new(std::collections::HashMap::new());
531}
532
533static NEXT_BRIDGE: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
534
535fn deliver_bridged<T: Send + 'static>(bridge: u64, event: T) {
536 let channel = BRIDGES.with(|bridges| bridges.borrow().get(&bridge).cloned());
537 let Some(channel) = channel else {
538 log::debug!("event bridge {bridge} is gone, one event dropped");
539 return;
540 };
541 if let Ok(channel) = channel.downcast::<EventChannel<T>>() {
542 channel.send(event);
543 }
544}
545
546struct Bridge<T: Send + 'static> {
547 id: u64,
548 channel: Rc<EventChannel<T>>,
549}
550
551impl<T: Send + 'static> Bridge<T> {
552 fn new() -> Self {
553 let id = NEXT_BRIDGE.fetch_add(1, Ordering::Relaxed);
554 let channel = Rc::new(EventChannel::<T>::new());
555 BRIDGES.with(|bridges| {
556 bridges
557 .borrow_mut()
558 .insert(id, Rc::clone(&channel) as Rc<dyn std::any::Any>)
559 });
560 Self { id, channel }
561 }
562}
563
564impl<T: Send + 'static> Drop for Bridge<T> {
565 fn drop(&mut self) {
566 BRIDGES.with(|bridges| bridges.borrow_mut().remove(&self.id));
567 self.channel.close();
568 }
569}
570
571#[allow(non_snake_case)]
579#[track_caller]
580pub fn rememberEventStream<T, K, R, S>(key: K, subscribe: S) -> EventStream<T>
581where
582 T: Send + 'static,
583 K: PartialEq + '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(crate::caller_location_key(), key, move |scope| {
593 #[cfg(not(target_arch = "wasm32"))]
594 let Some(dispatcher) = dispatcher else {
595 log::warn!("cranpose: an event stream was remembered without a runtime");
596 return scope.on_dispose(|| {});
597 };
598 let registration = subscribe(EventSender {
599 #[cfg(not(target_arch = "wasm32"))]
600 dispatcher,
601 bridge: id,
602 _events: std::marker::PhantomData,
603 });
604 scope.on_dispose(move || drop(registration))
605 });
606
607 stream
608}
609
610#[allow(non_snake_case)]
617pub async fn withBlocking<T, F>(work: F) -> T
618where
619 T: Send + 'static,
620 F: FnOnce() -> T + Send + 'static,
621{
622 #[cfg(not(target_arch = "wasm32"))]
623 {
624 let slot: Arc<Mutex<Option<T>>> = Arc::new(Mutex::new(None));
625 let done = Arc::new(AtomicBool::new(false));
626 let wakers: Arc<Mutex<Vec<Waker>>> = Arc::new(Mutex::new(Vec::new()));
627
628 let worker_slot = Arc::clone(&slot);
629 let worker_done = Arc::clone(&done);
630 let worker_wakers = Arc::clone(&wakers);
631 BlockingPool::get().submit(Box::new(move || {
632 let value = work();
633 *worker_slot
634 .lock()
635 .unwrap_or_else(|error| error.into_inner()) = Some(value);
636 worker_done.store(true, Ordering::Release);
637 for waker in worker_wakers
638 .lock()
639 .unwrap_or_else(|error| error.into_inner())
640 .drain(..)
641 {
642 waker.wake();
643 }
644 }));
645
646 BlockingWork { slot, done, wakers }.await
647 }
648 #[cfg(target_arch = "wasm32")]
649 {
650 work()
651 }
652}
653
654#[allow(non_snake_case)]
673pub fn launchBlocking<T>(work: impl FnOnce() -> T + Send + 'static, on_ui: impl FnOnce(T) + 'static)
674where
675 T: Send + 'static,
676{
677 let Some(runtime) = current_runtime_handle() else {
678 on_ui(work());
679 return;
680 };
681 let Some(continuation) = runtime.register_ui_cont(on_ui) else {
682 return;
683 };
684 let dispatcher = runtime.dispatcher();
685 #[cfg(not(target_arch = "wasm32"))]
686 BlockingPool::get().submit(Box::new(move || {
687 dispatcher.post_invoke(continuation, work());
688 }));
689 #[cfg(target_arch = "wasm32")]
690 dispatcher.post_invoke(continuation, work());
691}
692
693#[cfg(not(target_arch = "wasm32"))]
694struct BlockingPool {
695 sender: std::sync::mpsc::Sender<BlockingJob>,
696 receiver: Arc<Mutex<std::sync::mpsc::Receiver<BlockingJob>>>,
697 state: Arc<Mutex<PoolState>>,
698}
699
700#[cfg(not(target_arch = "wasm32"))]
701#[derive(Clone, Copy, Default)]
702struct PoolState {
703 alive: usize,
704 outstanding: usize,
705}
706
707#[cfg(not(target_arch = "wasm32"))]
708type BlockingJob = Box<dyn FnOnce() + Send + 'static>;
709
710#[cfg(not(target_arch = "wasm32"))]
711const MAX_BLOCKING_WORKERS: usize = 64;
712
713#[cfg(not(target_arch = "wasm32"))]
714const _: () = assert!(MAX_BLOCKING_WORKERS > 0 && MAX_BLOCKING_WORKERS <= 256);
715
716#[cfg(not(target_arch = "wasm32"))]
717impl BlockingPool {
718 fn get() -> &'static BlockingPool {
719 static POOL: OnceLock<BlockingPool> = OnceLock::new();
720 POOL.get_or_init(BlockingPool::new)
721 }
722
723 fn new() -> BlockingPool {
724 let (sender, receiver) = std::sync::mpsc::channel();
725 BlockingPool {
726 sender,
727 receiver: Arc::new(Mutex::new(receiver)),
728 state: Arc::new(Mutex::new(PoolState::default())),
729 }
730 }
731
732 fn submit(&self, job: BlockingJob) {
733 if self.take_slot() {
734 self.start_worker();
735 }
736 if let Err(returned) = self.sender.send(job) {
737 self.release_slot();
738 (returned.0)();
739 }
740 }
741
742 fn take_slot(&self) -> bool {
743 let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner());
744 state.outstanding += 1;
745 let grow = state.alive < state.outstanding && state.alive < MAX_BLOCKING_WORKERS;
746 if grow {
747 state.alive += 1;
748 }
749 grow
750 }
751
752 fn release_slot(&self) {
753 let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner());
754 state.outstanding = state.outstanding.saturating_sub(1);
755 }
756
757 fn start_worker(&self) {
758 let receiver = Arc::clone(&self.receiver);
759 let counters = Arc::clone(&self.state);
760 let started = std::thread::Builder::new()
761 .name("cranpose-blocking".to_string())
762 .spawn(move || {
763 loop {
764 let job = {
765 let queue = receiver.lock().unwrap_or_else(|error| error.into_inner());
766 queue.recv()
767 };
768 let Ok(job) = job else {
769 break;
770 };
771 job();
772 let mut counters = counters.lock().unwrap_or_else(|error| error.into_inner());
773 counters.outstanding = counters.outstanding.saturating_sub(1);
774 }
775 });
776 if started.is_err() {
777 let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner());
778 state.alive -= 1;
779 }
780 }
781}
782
783#[cfg(not(target_arch = "wasm32"))]
784struct BlockingWork<T> {
785 slot: Arc<Mutex<Option<T>>>,
786 done: Arc<AtomicBool>,
787 wakers: Arc<Mutex<Vec<Waker>>>,
788}
789
790#[cfg(not(target_arch = "wasm32"))]
791impl<T> Future for BlockingWork<T> {
792 type Output = T;
793
794 fn poll(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<T> {
795 if self.done.load(Ordering::Acquire)
796 && let Some(value) = self
797 .slot
798 .lock()
799 .unwrap_or_else(|error| error.into_inner())
800 .take()
801 {
802 return Poll::Ready(value);
803 }
804 self.wakers
805 .lock()
806 .unwrap_or_else(|error| error.into_inner())
807 .push(context.waker().clone());
808 if self.done.load(Ordering::Acquire)
809 && let Some(value) = self
810 .slot
811 .lock()
812 .unwrap_or_else(|error| error.into_inner())
813 .take()
814 {
815 return Poll::Ready(value);
816 }
817 Poll::Pending
818 }
819}
820
821#[allow(non_snake_case)]
827#[track_caller]
828pub fn produceState<T, K, F>(initial: T, key: K, producer: F) -> State<T>
829where
830 T: Clone + 'static,
831 K: PartialEq + 'static,
832 F: FnOnce(ProduceScope<T>) -> Pin<Box<dyn Future<Output = ()>>> + 'static,
833{
834 let state = remember(|| mutableStateOfNeverEqual(initial)).with(|state| *state);
835 let handle = ProduceScope { state };
836 crate::__launched_effect_async_impl(
837 crate::caller_location_key(),
838 std::panic::Location::caller().into(),
839 key,
840 move |_scope| producer(handle),
841 );
842 state.as_state()
843}
844
845pub struct ProduceScope<T: Clone + 'static> {
847 state: MutableState<T>,
848}
849
850impl<T: Clone + 'static> ProduceScope<T> {
851 pub fn set(&self, value: T) {
853 self.state.set(value);
854 }
855}
856
857#[cfg(test)]
858mod tests {
859 use super::*;
860
861 #[test]
862 fn a_delay_resolves_after_its_deadline() {
863 let started = Instant::now();
864 pollster::block_on(delay(Duration::from_millis(30)));
865 assert!(started.elapsed() >= Duration::from_millis(25));
866 }
867
868 #[test]
869 fn many_delays_share_one_timer_and_all_fire() {
870 let started = Instant::now();
871 pollster::block_on(async {
872 for _ in 0..4 {
873 delay(Duration::from_millis(5)).await;
874 }
875 });
876 assert!(started.elapsed() >= Duration::from_millis(15));
877 }
878
879 #[test]
880 fn an_elapsed_delay_is_ready_without_arming_the_timer() {
881 let mut future = Box::pin(Delay {
882 deadline: Instant::now() - Duration::from_millis(1),
883 armed: false,
884 fired: Arc::new(AtomicBool::new(false)),
885 });
886 let waker = Waker::noop().clone();
887 assert!(
888 future
889 .as_mut()
890 .poll(&mut Context::from_waker(&waker))
891 .is_ready()
892 );
893 }
894}
895
896#[cfg(test)]
897mod stream_tests {
898 use super::*;
899
900 #[test]
901 fn a_channel_wakes_its_collector_and_ends_when_closed() {
902 let channel: EventChannel<u32> = EventChannel::new();
903 let stream = channel.stream();
904
905 let mut pending = Box::pin(stream.next());
906 let waker = Waker::noop().clone();
907 let mut context = Context::from_waker(&waker);
908 assert!(pending.as_mut().poll(&mut context).is_pending());
909
910 channel.send(7);
911 assert_eq!(pending.as_mut().poll(&mut context), Poll::Ready(Some(7)));
912
913 channel.send(8);
914 channel.close();
915 assert_eq!(pollster::block_on(stream.next()), Some(8));
916 assert_eq!(pollster::block_on(stream.next()), None);
917 assert_eq!(stream.delivered(), 2);
918 }
919
920 #[test]
921 fn an_event_goes_to_exactly_one_collector() {
922 let channel: EventChannel<u32> = EventChannel::new();
923 let first = channel.stream();
924 let second = first.clone();
925 channel.send(1);
926 channel.close();
927 assert_eq!(pollster::block_on(first.next()), Some(1));
928 assert_eq!(pollster::block_on(second.next()), None);
929 }
930
931 #[test]
932 fn sending_after_close_is_ignored() {
933 let channel: EventChannel<u32> = EventChannel::new();
934 let stream = channel.stream();
935 channel.close();
936 channel.send(1);
937 assert_eq!(pollster::block_on(stream.next()), None);
938 assert_eq!(channel.pending(), 0);
939 }
940
941 #[test]
942 fn blocking_work_resolves_with_its_result() {
943 let doubled = pollster::block_on(withBlocking(|| 21 * 2));
944 assert_eq!(doubled, 42);
945 }
946}
947
948#[cfg(test)]
949mod timer_race_tests {
950 use super::*;
951
952 #[test]
953 fn concurrent_arming_never_loses_a_wake_up() {
954 let rounds = 40;
955 let threads: Vec<_> = (0..8)
956 .map(|worker| {
957 std::thread::spawn(move || {
958 for round in 0..rounds {
959 let millis = 1 + ((worker + round) % 5) as u64;
960 pollster::block_on(delay(Duration::from_millis(millis)));
961 }
962 })
963 })
964 .collect();
965 for thread in threads {
966 thread.join().expect("every waiter is woken");
967 }
968 }
969
970 #[cfg(not(target_arch = "wasm32"))]
971 #[test]
972 fn blocking_work_reuses_its_threads_instead_of_one_per_call() {
973 use std::{collections::HashSet, sync::mpsc};
974
975 let pool = BlockingPool::new();
976
977 let (sender, receiver) = mpsc::channel();
978 for _ in 0..16 {
979 let done = Arc::new((Mutex::new(false), Condvar::new()));
980 let waiter = Arc::clone(&done);
981 let sender = sender.clone();
982 pool.submit(Box::new(move || {
983 let _ = sender.send(std::thread::current().id());
984 let (lock, signal) = &*done;
985 *lock.lock().unwrap_or_else(|error| error.into_inner()) = true;
986 signal.notify_all();
987 }));
988 let (lock, signal) = &*waiter;
989 let mut finished = lock.lock().unwrap_or_else(|error| error.into_inner());
990 while !*finished {
991 finished = signal
992 .wait(finished)
993 .unwrap_or_else(|error| error.into_inner());
994 }
995 }
996 drop(sender);
997
998 let threads: HashSet<_> = receiver.iter().collect();
999 assert!(
1000 threads.len() < 16,
1001 "sixteen serial jobs used {} threads; the pool is not reusing them",
1002 threads.len()
1003 );
1004 }
1005
1006 #[cfg(not(target_arch = "wasm32"))]
1007 #[test]
1008 fn blocking_work_grows_so_one_slow_job_cannot_hold_up_another() {
1009 let pool = BlockingPool::new();
1010 let started = Arc::new((Mutex::new(0usize), Condvar::new()));
1011 let release = Arc::new((Mutex::new(false), Condvar::new()));
1012
1013 for _ in 0..4 {
1014 let started = Arc::clone(&started);
1015 let release = Arc::clone(&release);
1016 pool.submit(Box::new(move || {
1017 {
1018 let (count, signal) = &*started;
1019 *count.lock().unwrap_or_else(|error| error.into_inner()) += 1;
1020 signal.notify_all();
1021 }
1022 let (held, signal) = &*release;
1023 let mut go = held.lock().unwrap_or_else(|error| error.into_inner());
1024 while !*go {
1025 go = signal.wait(go).unwrap_or_else(|error| error.into_inner());
1026 }
1027 }));
1028 }
1029
1030 let (count, signal) = &*started;
1031 let mut running = count.lock().unwrap_or_else(|error| error.into_inner());
1032 while *running < 4 {
1033 running = signal
1034 .wait(running)
1035 .unwrap_or_else(|error| error.into_inner());
1036 }
1037 drop(running);
1038
1039 let (held, signal) = &*release;
1040 *held.lock().unwrap_or_else(|error| error.into_inner()) = true;
1041 signal.notify_all();
1042 }
1043}