summaryrefslogtreecommitdiffstats
path: root/third_party/rust/cc/src/parallel/async_executor.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/cc/src/parallel/async_executor.rs')
-rw-r--r--third_party/rust/cc/src/parallel/async_executor.rs118
1 files changed, 118 insertions, 0 deletions
diff --git a/third_party/rust/cc/src/parallel/async_executor.rs b/third_party/rust/cc/src/parallel/async_executor.rs
new file mode 100644
index 0000000000..9ebd1ad562
--- /dev/null
+++ b/third_party/rust/cc/src/parallel/async_executor.rs
@@ -0,0 +1,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
+ };
+ }
+}