summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cc/src/parallel/async_executor.rs
blob: 9ebd1ad56205960e302c2a076bacc21826da1874 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
use std::{
    cell::Cell,
    future::Future,
    pin::Pin,
    ptr,
    task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
    thread,
    time::Duration,
};

use crate::Error;

const NOOP_WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(
    // Cloning just returns a new no-op raw waker
    |_| NOOP_RAW_WAKER,
    // `wake` does nothing
    |_| {},
    // `wake_by_ref` does nothing
    |_| {},
    // Dropping does nothing as we don't allocate anything
    |_| {},
);
const NOOP_RAW_WAKER: RawWaker = RawWaker::new(ptr::null(), &NOOP_WAKER_VTABLE);

#[derive(Default)]
pub(crate) struct YieldOnce(bool);

impl Future for YieldOnce {
    type Output = ();

    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
        let flag = &mut std::pin::Pin::into_inner(self).0;
        if !*flag {
            *flag = true;
            Poll::Pending
        } else {
            Poll::Ready(())
        }
    }
}

/// Execute the futures and return when they are all done.
///
/// Here we use our own homebrew async executor since cc is used in the build
/// script of many popular projects, pulling in additional dependencies would
/// significantly slow down its compilation.
pub(crate) fn block_on<Fut1, Fut2>(
    mut fut1: Fut1,
    mut fut2: Fut2,
    has_made_progress: &Cell<bool>,
) -> Result<(), Error>
where
    Fut1: Future<Output = Result<(), Error>>,
    Fut2: Future<Output = Result<(), Error>>,
{
    // Shadows the future so that it can never be moved and is guaranteed
    // to be pinned.
    //
    // The same trick used in `pin!` macro.
    //
    // TODO: Once MSRV is bumped to 1.68, replace this with `std::pin::pin!`
    let mut fut1 = Some(unsafe { Pin::new_unchecked(&mut fut1) });
    let mut fut2 = Some(unsafe { Pin::new_unchecked(&mut fut2) });

    // TODO: Once `Waker::noop` stablised and our MSRV is bumped to the version
    // which it is stablised, replace this with `Waker::noop`.
    let waker = unsafe { Waker::from_raw(NOOP_RAW_WAKER) };
    let mut context = Context::from_waker(&waker);

    let mut backoff_cnt = 0;

    loop {
        has_made_progress.set(false);

        if let Some(fut) = fut2.as_mut() {
            if let Poll::Ready(res) = fut.as_mut().poll(&mut context) {
                fut2 = None;
                res?;
            }
        }

        if let Some(fut) = fut1.as_mut() {
            if let Poll::Ready(res) = fut.as_mut().poll(&mut context) {
                fut1 = None;
                res?;
            }
        }

        if fut1.is_none() && fut2.is_none() {
            return Ok(());
        }

        if !has_made_progress.get() {
            if backoff_cnt > 3 {
                // We have yielded at least three times without making'
                // any progress, so we will sleep for a while.
                let duration = Duration::from_millis(100 * (backoff_cnt - 3).min(10));
                thread::sleep(duration);
            } else {
                // Given that we spawned a lot of compilation tasks, it is unlikely
                // that OS cannot find other ready task to execute.
                //
                // If all of them are done, then we will yield them and spawn more,
                // or simply return.
                //
                // Thus this will not be turned into a busy-wait loop and it will not
                // waste CPU resource.
                thread::yield_now();
            }
        }

        backoff_cnt = if has_made_progress.get() {
            0
        } else {
            backoff_cnt + 1
        };
    }
}