Add DeterministicExecutor::block_on(duration, future)

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
Co-Authored-By: Max Brunsfeld <max@zed.dev>
This commit is contained in:
Antonio Scandurra 2021-07-20 19:20:50 +02:00
parent 3d8c59af31
commit 3d3a14b650
3 changed files with 50 additions and 33 deletions

1
Cargo.lock generated
View file

@ -2159,6 +2159,7 @@ dependencies = [
"etagere", "etagere",
"font-kit", "font-kit",
"foreign-types", "foreign-types",
"futures",
"gpui_macros", "gpui_macros",
"log", "log",
"metal", "metal",

View file

@ -9,6 +9,7 @@ async-task = "4.0.3"
backtrace = "0.3" backtrace = "0.3"
ctor = "0.1" ctor = "0.1"
etagere = "0.2" etagere = "0.2"
futures = "0.3"
gpui_macros = { path = "../gpui_macros" } gpui_macros = { path = "../gpui_macros" }
log = "0.4" log = "0.4"
num_cpus = "1.13" num_cpus = "1.13"

View file

@ -2,6 +2,7 @@ use anyhow::{anyhow, Result};
use async_task::Runnable; use async_task::Runnable;
pub use async_task::Task; pub use async_task::Task;
use backtrace::{Backtrace, BacktraceFmt, BytesOrWideString}; use backtrace::{Backtrace, BacktraceFmt, BytesOrWideString};
use futures::task::noop_waker;
use parking_lot::Mutex; use parking_lot::Mutex;
use rand::prelude::*; use rand::prelude::*;
use smol::{channel, prelude::*, Executor}; use smol::{channel, prelude::*, Executor};
@ -13,13 +14,14 @@ use std::{
rc::Rc, rc::Rc,
sync::{ sync::{
atomic::{AtomicBool, Ordering::SeqCst}, atomic::{AtomicBool, Ordering::SeqCst},
mpsc::Sender,
Arc, Arc,
}, },
task::{Context, Poll},
thread, thread,
time::Duration,
}; };
use crate::platform; use crate::{platform, util};
pub enum Foreground { pub enum Foreground {
Platform { Platform {
@ -43,7 +45,6 @@ struct DeterministicState {
seed: u64, seed: u64,
scheduled: Vec<(Runnable, Backtrace)>, scheduled: Vec<(Runnable, Backtrace)>,
spawned_from_foreground: Vec<(Runnable, Backtrace)>, spawned_from_foreground: Vec<(Runnable, Backtrace)>,
waker: Option<Sender<()>>,
} }
pub struct Deterministic(Arc<Mutex<DeterministicState>>); pub struct Deterministic(Arc<Mutex<DeterministicState>>);
@ -55,7 +56,6 @@ impl Deterministic {
seed, seed,
scheduled: Default::default(), scheduled: Default::default(),
spawned_from_foreground: Default::default(), spawned_from_foreground: Default::default(),
waker: None,
}))) })))
} }
@ -75,9 +75,6 @@ impl Deterministic {
} else { } else {
state.spawned_from_foreground.push((runnable, backtrace)); state.spawned_from_foreground.push((runnable, backtrace));
} }
if let Some(waker) = state.waker.as_ref() {
waker.send(()).ok();
}
}); });
runnable.schedule(); runnable.schedule();
task task
@ -91,11 +88,7 @@ impl Deterministic {
let backtrace = Backtrace::new_unresolved(); let backtrace = Backtrace::new_unresolved();
let state = self.0.clone(); let state = self.0.clone();
let (runnable, task) = async_task::spawn(future, move |runnable| { let (runnable, task) = async_task::spawn(future, move |runnable| {
let mut state = state.lock(); state.lock().scheduled.push((runnable, backtrace.clone()));
state.scheduled.push((runnable, backtrace.clone()));
if let Some(waker) = state.waker.as_ref() {
waker.send(()).ok();
}
}); });
runnable.schedule(); runnable.schedule();
task task
@ -106,43 +99,49 @@ impl Deterministic {
T: 'static, T: 'static,
F: Future<Output = T> + 'static, F: Future<Output = T> + 'static,
{ {
let (wake_tx, wake_rx) = std::sync::mpsc::channel(); self.block_on(usize::MAX, future).unwrap()
let state = self.0.clone(); }
state.lock().waker = Some(wake_tx);
let (output_tx, output_rx) = std::sync::mpsc::channel(); pub fn block_on<F, T>(&self, max_ticks: usize, future: F) -> Option<T>
self.spawn_from_foreground(async move { where
let output = future.await; T: 'static,
output_tx.send(output).unwrap(); F: Future<Output = T>,
}) {
.detach(); smol::pin!(future);
let waker = noop_waker();
let mut cx = Context::from_waker(&waker);
let mut trace = Trace::default(); let mut trace = Trace::default();
loop { for _ in 0..max_ticks {
if let Ok(value) = output_rx.try_recv() {
state.lock().waker = None;
return value;
}
wake_rx.recv().unwrap();
let runnable = { let runnable = {
let state = &mut *state.lock(); let state = &mut *self.0.lock();
let ix = state let runnable_count = state.scheduled.len() + state.spawned_from_foreground.len();
.rng let ix = state.rng.gen_range(0..=runnable_count);
.gen_range(0..state.scheduled.len() + state.spawned_from_foreground.len());
if ix < state.scheduled.len() { if ix < state.scheduled.len() {
let (_, backtrace) = &state.scheduled[ix]; let (_, backtrace) = &state.scheduled[ix];
trace.record(&state, backtrace.clone()); trace.record(&state, backtrace.clone());
state.scheduled.remove(ix).0 state.scheduled.remove(ix).0
} else { } else if ix < runnable_count {
let (_, backtrace) = &state.spawned_from_foreground[0]; let (_, backtrace) = &state.spawned_from_foreground[0];
trace.record(&state, backtrace.clone()); trace.record(&state, backtrace.clone());
state.spawned_from_foreground.remove(0).0 state.spawned_from_foreground.remove(0).0
} else {
if let Poll::Ready(result) = future.as_mut().poll(&mut cx) {
return Some(result);
}
if state.scheduled.is_empty() && state.spawned_from_foreground.is_empty() {
panic!("detected non-determinism in deterministic executor");
} else {
continue;
}
} }
}; };
runnable.run(); runnable.run();
} }
None
} }
} }
@ -354,6 +353,22 @@ impl Background {
} }
} }
pub fn block_on<F, T>(&self, timeout: Duration, future: F) -> Option<T>
where
T: 'static,
F: Future<Output = T>,
{
match self {
Self::Production { .. } => {
smol::block_on(async move { util::timeout(timeout, future).await.ok() })
}
Self::Deterministic(executor) => {
let max_ticks = executor.0.lock().rng.gen_range(1..=1000);
executor.block_on(max_ticks, future)
}
}
}
pub async fn scoped<'scope, F>(&self, scheduler: F) pub async fn scoped<'scope, F>(&self, scheduler: F)
where where
F: FnOnce(&mut Scope<'scope>), F: FnOnce(&mut Scope<'scope>),