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( mut fut1: Fut1, mut fut2: Fut2, has_made_progress: &Cell, ) -> Result<(), Error> where Fut1: Future>, Fut2: Future>, { // 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 }; } }