Fix hangs in new dispatcher

Co-authored-by: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Max Brunsfeld 2023-11-01 17:11:42 -07:00
parent 90facc051a
commit 6ee93125d0
11 changed files with 93 additions and 44 deletions

View file

@ -70,6 +70,10 @@ impl TestAppContext {
&self.background_executor &self.background_executor
} }
pub fn foreground_executor(&self) -> &ForegroundExecutor {
&self.foreground_executor
}
pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R { pub fn update<R>(&self, f: impl FnOnce(&mut AppContext) -> R) -> R {
let mut cx = self.app.borrow_mut(); let mut cx = self.app.borrow_mut();
cx.update(f) cx.update(f)

View file

@ -88,16 +88,7 @@ impl BackgroundExecutor {
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R { pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
let (runnable, task) = unsafe { self.block_internal(false, future)
async_task::spawn_unchecked(future, {
let dispatcher = self.dispatcher.clone();
move |runnable| dispatcher.dispatch_on_main_thread(runnable)
})
};
runnable.schedule();
self.block_internal(false, task)
} }
pub fn block<R>(&self, future: impl Future<Output = R>) -> R { pub fn block<R>(&self, future: impl Future<Output = R>) -> R {
@ -109,20 +100,19 @@ impl BackgroundExecutor {
background_only: bool, background_only: bool,
future: impl Future<Output = R>, future: impl Future<Output = R>,
) -> R { ) -> R {
dbg!("block_internal");
pin_mut!(future); pin_mut!(future);
let (parker, unparker) = parking::pair(); let unparker = self.dispatcher.unparker();
let awoken = Arc::new(AtomicBool::new(false)); let awoken = Arc::new(AtomicBool::new(false));
let awoken2 = awoken.clone();
let waker = waker_fn(move || { let waker = waker_fn({
dbg!("WAKING UP."); let awoken = awoken.clone();
awoken2.store(true, SeqCst); move || {
awoken.store(true, SeqCst);
unparker.unpark(); unparker.unpark();
}
}); });
let mut cx = std::task::Context::from_waker(&waker); let mut cx = std::task::Context::from_waker(&waker);
dbg!("BOOOP");
loop { loop {
match future.as_mut().poll(&mut cx) { match future.as_mut().poll(&mut cx) {
Poll::Ready(result) => return result, Poll::Ready(result) => return result,
@ -143,9 +133,8 @@ impl BackgroundExecutor {
panic!("parked with nothing left to run\n{:?}", backtrace_message) panic!("parked with nothing left to run\n{:?}", backtrace_message)
} }
} }
dbg!("PARKING!");
parker.park(); self.dispatcher.park();
dbg!("CONTINUING!");
} }
} }
} }

View file

@ -12,6 +12,7 @@ use crate::{
use anyhow::anyhow; use anyhow::anyhow;
use async_task::Runnable; use async_task::Runnable;
use futures::channel::oneshot; use futures::channel::oneshot;
use parking::Unparker;
use seahash::SeaHasher; use seahash::SeaHasher;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::borrow::Cow; use std::borrow::Cow;
@ -163,6 +164,8 @@ pub trait PlatformDispatcher: Send + Sync {
fn dispatch_on_main_thread(&self, runnable: Runnable); fn dispatch_on_main_thread(&self, runnable: Runnable);
fn dispatch_after(&self, duration: Duration, runnable: Runnable); fn dispatch_after(&self, duration: Duration, runnable: Runnable);
fn poll(&self, background_only: bool) -> bool; fn poll(&self, background_only: bool) -> bool;
fn park(&self);
fn unparker(&self) -> Unparker;
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
fn as_test(&self) -> Option<&TestDispatcher> { fn as_test(&self) -> Option<&TestDispatcher> {

View file

@ -9,8 +9,11 @@ use objc::{
runtime::{BOOL, YES}, runtime::{BOOL, YES},
sel, sel_impl, sel, sel_impl,
}; };
use parking::{Parker, Unparker};
use parking_lot::Mutex;
use std::{ use std::{
ffi::c_void, ffi::c_void,
sync::Arc,
time::{Duration, SystemTime}, time::{Duration, SystemTime},
}; };
@ -20,7 +23,17 @@ pub fn dispatch_get_main_queue() -> dispatch_queue_t {
unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t } unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
} }
pub struct MacDispatcher; pub struct MacDispatcher {
parker: Arc<Mutex<Parker>>,
}
impl MacDispatcher {
pub fn new() -> Self {
MacDispatcher {
parker: Arc::new(Mutex::new(Parker::new())),
}
}
}
impl PlatformDispatcher for MacDispatcher { impl PlatformDispatcher for MacDispatcher {
fn is_main_thread(&self) -> bool { fn is_main_thread(&self) -> bool {
@ -71,6 +84,14 @@ impl PlatformDispatcher for MacDispatcher {
fn poll(&self, _background_only: bool) -> bool { fn poll(&self, _background_only: bool) -> bool {
false false
} }
fn park(&self) {
self.parker.lock().park()
}
fn unparker(&self) -> Unparker {
self.parker.lock().unparker()
}
} }
extern "C" fn trampoline(runnable: *mut c_void) { extern "C" fn trampoline(runnable: *mut c_void) {

View file

@ -165,10 +165,10 @@ pub struct MacPlatformState {
impl MacPlatform { impl MacPlatform {
pub fn new() -> Self { pub fn new() -> Self {
let dispatcher = Arc::new(MacDispatcher); let dispatcher = Arc::new(MacDispatcher::new());
Self(Mutex::new(MacPlatformState { Self(Mutex::new(MacPlatformState {
background_executor: BackgroundExecutor::new(dispatcher.clone()), background_executor: BackgroundExecutor::new(dispatcher.clone()),
foreground_executor: ForegroundExecutor::new(dispatcher.clone()), foreground_executor: ForegroundExecutor::new(dispatcher),
text_system: Arc::new(MacTextSystem::new()), text_system: Arc::new(MacTextSystem::new()),
display_linker: MacDisplayLinker::new(), display_linker: MacDisplayLinker::new(),
pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) }, pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },

View file

@ -2,6 +2,7 @@ use crate::PlatformDispatcher;
use async_task::Runnable; use async_task::Runnable;
use backtrace::Backtrace; use backtrace::Backtrace;
use collections::{HashMap, VecDeque}; use collections::{HashMap, VecDeque};
use parking::{Parker, Unparker};
use parking_lot::Mutex; use parking_lot::Mutex;
use rand::prelude::*; use rand::prelude::*;
use std::{ use std::{
@ -19,6 +20,8 @@ struct TestDispatcherId(usize);
pub struct TestDispatcher { pub struct TestDispatcher {
id: TestDispatcherId, id: TestDispatcherId,
state: Arc<Mutex<TestDispatcherState>>, state: Arc<Mutex<TestDispatcherState>>,
parker: Arc<Mutex<Parker>>,
unparker: Unparker,
} }
struct TestDispatcherState { struct TestDispatcherState {
@ -35,6 +38,7 @@ struct TestDispatcherState {
impl TestDispatcher { impl TestDispatcher {
pub fn new(random: StdRng) -> Self { pub fn new(random: StdRng) -> Self {
let (parker, unparker) = parking::pair();
let state = TestDispatcherState { let state = TestDispatcherState {
random, random,
foreground: HashMap::default(), foreground: HashMap::default(),
@ -50,6 +54,8 @@ impl TestDispatcher {
TestDispatcher { TestDispatcher {
id: TestDispatcherId(0), id: TestDispatcherId(0),
state: Arc::new(Mutex::new(state)), state: Arc::new(Mutex::new(state)),
parker: Arc::new(Mutex::new(parker)),
unparker,
} }
} }
@ -129,6 +135,8 @@ impl Clone for TestDispatcher {
Self { Self {
id: TestDispatcherId(id), id: TestDispatcherId(id),
state: self.state.clone(), state: self.state.clone(),
parker: self.parker.clone(),
unparker: self.unparker.clone(),
} }
} }
} }
@ -140,6 +148,7 @@ impl PlatformDispatcher for TestDispatcher {
fn dispatch(&self, runnable: Runnable) { fn dispatch(&self, runnable: Runnable) {
self.state.lock().background.push(runnable); self.state.lock().background.push(runnable);
self.unparker.unpark();
} }
fn dispatch_on_main_thread(&self, runnable: Runnable) { fn dispatch_on_main_thread(&self, runnable: Runnable) {
@ -149,6 +158,7 @@ impl PlatformDispatcher for TestDispatcher {
.entry(self.id) .entry(self.id)
.or_default() .or_default()
.push_back(runnable); .push_back(runnable);
self.unparker.unpark();
} }
fn dispatch_after(&self, duration: std::time::Duration, runnable: Runnable) { fn dispatch_after(&self, duration: std::time::Duration, runnable: Runnable) {
@ -215,6 +225,14 @@ impl PlatformDispatcher for TestDispatcher {
true true
} }
fn park(&self) {
self.parker.lock().park();
}
fn unparker(&self) -> Unparker {
self.unparker.clone()
}
fn as_test(&self) -> Option<&TestDispatcher> { fn as_test(&self) -> Option<&TestDispatcher> {
Some(self) Some(self)
} }

View file

@ -28,7 +28,7 @@ pub fn run_test(
} }
let result = panic::catch_unwind(|| { let result = panic::catch_unwind(|| {
let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(seed)); let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(seed));
test_fn(dispatcher.clone(), seed); test_fn(dispatcher, seed);
}); });
match result { match result {

View file

@ -91,7 +91,7 @@ pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
} }
Some("BackgroundExecutor") => { Some("BackgroundExecutor") => {
inner_fn_args.extend(quote!(gpui2::BackgroundExecutor::new( inner_fn_args.extend(quote!(gpui2::BackgroundExecutor::new(
std::sync::Arc::new(dispatcher.clone()) std::sync::Arc::new(dispatcher.clone()),
),)); ),));
continue; continue;
} }

View file

@ -877,17 +877,14 @@ impl Project {
) )
}); });
for path in root_paths { for path in root_paths {
dbg!(&path);
let (tree, _) = project let (tree, _) = project
.update(cx, |project, cx| { .update(cx, |project, cx| {
project.find_or_create_local_worktree(path, true, cx) project.find_or_create_local_worktree(path, true, cx)
}) })
.await .await
.unwrap(); .unwrap();
dbg!("aaa");
tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete()) tree.update(cx, |tree, _| tree.as_local().unwrap().scan_complete())
.await; .await;
dbg!("bbb");
} }
project project
} }
@ -5993,10 +5990,8 @@ impl Project {
) -> Task<Result<(Model<Worktree>, PathBuf)>> { ) -> Task<Result<(Model<Worktree>, PathBuf)>> {
let abs_path = abs_path.as_ref(); let abs_path = abs_path.as_ref();
if let Some((tree, relative_path)) = self.find_local_worktree(abs_path, cx) { if let Some((tree, relative_path)) = self.find_local_worktree(abs_path, cx) {
dbg!("shortcut");
Task::ready(Ok((tree, relative_path))) Task::ready(Ok((tree, relative_path)))
} else { } else {
dbg!("long cut");
let worktree = self.create_local_worktree(abs_path, visible, cx); let worktree = self.create_local_worktree(abs_path, visible, cx);
cx.background_executor() cx.background_executor()
.spawn(async move { Ok((worktree.await?, PathBuf::new())) }) .spawn(async move { Ok((worktree.await?, PathBuf::new())) })

View file

@ -15,6 +15,36 @@ use std::{os, task::Poll};
use unindent::Unindent as _; use unindent::Unindent as _;
use util::{assert_set_eq, test::temp_tree}; use util::{assert_set_eq, test::temp_tree};
#[gpui2::test]
async fn test_block_via_channel(cx: &mut gpui2::TestAppContext) {
cx.executor().allow_parking();
let (tx, mut rx) = futures::channel::mpsc::unbounded();
let _thread = std::thread::spawn(move || {
std::fs::metadata("/Users").unwrap();
std::thread::sleep(Duration::from_millis(1000));
tx.unbounded_send(1).unwrap();
});
rx.next().await.unwrap();
}
#[gpui2::test]
async fn test_block_via_smol(cx: &mut gpui2::TestAppContext) {
cx.executor().allow_parking();
let io_task = smol::unblock(move || {
println!("sleeping on thread {:?}", std::thread::current().id());
std::thread::sleep(Duration::from_millis(10));
1
});
let task = cx.foreground_executor().spawn(async move {
io_task.await;
});
task.await;
}
#[gpui2::test] #[gpui2::test]
async fn test_symlinks(cx: &mut gpui2::TestAppContext) { async fn test_symlinks(cx: &mut gpui2::TestAppContext) {
init_test(cx); init_test(cx);
@ -35,8 +65,6 @@ async fn test_symlinks(cx: &mut gpui2::TestAppContext) {
} }
})); }));
dbg!("GOT HERE");
let root_link_path = dir.path().join("root_link"); let root_link_path = dir.path().join("root_link");
os::unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap(); os::unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
os::unix::fs::symlink( os::unix::fs::symlink(
@ -45,11 +73,8 @@ async fn test_symlinks(cx: &mut gpui2::TestAppContext) {
) )
.unwrap(); .unwrap();
dbg!("GOT HERE 2");
let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await; let project = Project::test(Arc::new(RealFs), [root_link_path.as_ref()], cx).await;
dbg!("GOT HERE 2.5");
project.update(cx, |project, cx| { project.update(cx, |project, cx| {
let tree = project.worktrees().next().unwrap().read(cx); let tree = project.worktrees().next().unwrap().read(cx);
assert_eq!(tree.file_count(), 5); assert_eq!(tree.file_count(), 5);
@ -58,8 +83,6 @@ async fn test_symlinks(cx: &mut gpui2::TestAppContext) {
tree.inode_for_path("finnochio/grape") tree.inode_for_path("finnochio/grape")
); );
}); });
dbg!("GOT HERE 3");
} }
#[gpui2::test] #[gpui2::test]
@ -2706,7 +2729,7 @@ async fn test_save_as(cx: &mut gpui2::TestAppContext) {
#[gpui2::test(retries = 5)] #[gpui2::test(retries = 5)]
async fn test_rescan_and_remote_updates(cx: &mut gpui2::TestAppContext) { async fn test_rescan_and_remote_updates(cx: &mut gpui2::TestAppContext) {
init_test(cx); init_test(cx);
// cx.executor().allow_parking(); cx.executor().allow_parking();
let dir = temp_tree(json!({ let dir = temp_tree(json!({
"a": { "a": {

View file

@ -297,15 +297,12 @@ impl Worktree {
// After determining whether the root entry is a file or a directory, populate the // After determining whether the root entry is a file or a directory, populate the
// snapshot's "root name", which will be used for the purpose of fuzzy matching. // snapshot's "root name", which will be used for the purpose of fuzzy matching.
let abs_path = path.into(); let abs_path = path.into();
eprintln!("get root metadata");
let metadata = fs let metadata = fs
.metadata(&abs_path) .metadata(&abs_path)
.await .await
.context("failed to stat worktree path")?; .context("failed to stat worktree path")?;
eprintln!("got root metadata");
cx.build_model(move |cx: &mut ModelContext<Worktree>| { cx.build_model(move |cx: &mut ModelContext<Worktree>| {
let root_name = abs_path let root_name = abs_path
.file_name() .file_name()
@ -4067,13 +4064,12 @@ impl WorktreeModelHandle for Model<Worktree> {
fs.create_file(&root_path.join(filename), Default::default()) fs.create_file(&root_path.join(filename), Default::default())
.await .await
.unwrap(); .unwrap();
cx.executor().run_until_parked();
assert!(tree.update(cx, |tree, _| tree.entry_for_path(filename).is_some())); assert!(tree.update(cx, |tree, _| tree.entry_for_path(filename).is_some()));
fs.remove_file(&root_path.join(filename), Default::default()) fs.remove_file(&root_path.join(filename), Default::default())
.await .await
.unwrap(); .unwrap();
cx.executor().run_until_parked();
assert!(tree.update(cx, |tree, _| tree.entry_for_path(filename).is_none())); assert!(tree.update(cx, |tree, _| tree.entry_for_path(filename).is_none()));
cx.update(|cx| tree.read(cx).as_local().unwrap().scan_complete()) cx.update(|cx| tree.read(cx).as_local().unwrap().scan_complete())