cc/parallel/
async_executor.rs

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(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 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}