1use std::{
2 cell::Cell,
3 future::Future,
4 pin::Pin,
5 ptr,
6 task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
7 thread,
8 time::Duration,
9};
10
11use crate::Error;
12
13const NOOP_WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(
14 // Cloning just returns a new no-op raw waker
15 |_| NOOP_RAW_WAKER,
16 // `wake` does nothing
17 |_| {},
18 // `wake_by_ref` does nothing
19 |_| {},
20 // Dropping does nothing as we don't allocate anything
21 |_| {},
22);
23const NOOP_RAW_WAKER: RawWaker = RawWaker::new(data:ptr::null(), &NOOP_WAKER_VTABLE);
24
25#[derive(Default)]
26pub(crate) struct YieldOnce(bool);
27
28impl Future for YieldOnce {
29 type Output = ();
30
31 fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
32 let flag: &mut bool = &mut std::pin::Pin::into_inner(self).0;
33 if !*flag {
34 *flag = true;
35 Poll::Pending
36 } else {
37 Poll::Ready(())
38 }
39 }
40}
41
42/// Execute the futures and return when they are all done.
43///
44/// Here we use our own homebrew async executor since cc is used in the build
45/// script of many popular projects, pulling in additional dependencies would
46/// significantly slow down its compilation.
47pub(crate) fn block_on<Fut1, Fut2>(
48 mut fut1: Fut1,
49 mut fut2: Fut2,
50 has_made_progress: &Cell<bool>,
51) -> Result<(), Error>
52where
53 Fut1: Future<Output = Result<(), Error>>,
54 Fut2: Future<Output = Result<(), Error>>,
55{
56 // Shadows the future so that it can never be moved and is guaranteed
57 // to be pinned.
58 //
59 // The same trick used in `pin!` macro.
60 //
61 // TODO: Once MSRV is bumped to 1.68, replace this with `std::pin::pin!`
62 let mut fut1 = Some(unsafe { Pin::new_unchecked(&mut fut1) });
63 let mut fut2 = Some(unsafe { Pin::new_unchecked(&mut fut2) });
64
65 // TODO: Once `Waker::noop` stablised and our MSRV is bumped to the version
66 // which it is stablised, replace this with `Waker::noop`.
67 let waker = unsafe { Waker::from_raw(NOOP_RAW_WAKER) };
68 let mut context = Context::from_waker(&waker);
69
70 let mut backoff_cnt = 0;
71
72 loop {
73 has_made_progress.set(false);
74
75 if let Some(fut) = fut2.as_mut() {
76 if let Poll::Ready(res) = fut.as_mut().poll(&mut context) {
77 fut2 = None;
78 res?;
79 }
80 }
81
82 if let Some(fut) = fut1.as_mut() {
83 if let Poll::Ready(res) = fut.as_mut().poll(&mut context) {
84 fut1 = None;
85 res?;
86 }
87 }
88
89 if fut1.is_none() && fut2.is_none() {
90 return Ok(());
91 }
92
93 if !has_made_progress.get() {
94 if backoff_cnt > 3 {
95 // We have yielded at least three times without making'
96 // any progress, so we will sleep for a while.
97 let duration = Duration::from_millis(100 * (backoff_cnt - 3).min(10));
98 thread::sleep(duration);
99 } else {
100 // Given that we spawned a lot of compilation tasks, it is unlikely
101 // that OS cannot find other ready task to execute.
102 //
103 // If all of them are done, then we will yield them and spawn more,
104 // or simply return.
105 //
106 // Thus this will not be turned into a busy-wait loop and it will not
107 // waste CPU resource.
108 thread::yield_now();
109 }
110 }
111
112 backoff_cnt = if has_made_progress.get() {
113 0
114 } else {
115 backoff_cnt + 1
116 };
117 }
118}
119