Replace async-watch
with a custom watch (#32245)
The `async-watch` crate doesn't seem to be maintained and we noticed several panics coming from it, such as: ``` [bug] failed to observe change after notificaton. zed::reliability::init_panic_hook::{{closure}}::hea8cdcb6299fad6b+154543526 std::panicking::rust_panic_with_hook::h33b18b24045abff4+127578547 std::panicking::begin_panic_handler::{{closure}}::hf8313cc2fd0126bc+127577770 std::sys::backtrace::__rust_end_short_backtrace::h57fe07c8aea5c98a+127571385 __rustc[95feac21a9532783]::rust_begin_unwind+127576909 core::panicking::panic_fmt::hd54fb667be51beea+9433328 core::option::expect_failed::h8456634a3dada3e4+9433291 assistant_tools::edit_agent::EditAgent::apply_edit_chunks::{{closure}}::habe2e1a32b267fd4+26921553 gpui::app::async_context::AsyncApp::spawn::{{closure}}::h12f5f25757f572ea+25923441 async_task::raw::RawTask<F,T,S,M>::run::h3cca0d402690ccba+25186815 <gpui::platform::linux::x11::client::X11Client as gpui::platform::linux::platform::LinuxClient>::run::h26264aefbcfbc14b+73961666 gpui::platform::linux::platform::<impl gpui::platform::Platform for P>::run::hb12dcd4abad715b5+73562509 gpui::app::Application::run::h0f936a5f855a3f9f+150676820 zed::main::ha17f9a25fe257d35+154788471 std::sys::backtrace::__rust_begin_short_backtrace::h1edd02429370b2bd+154624579 std::rt::lang_start::{{closure}}::h3d2e300f10059b0a+154264777 std::rt::lang_start_internal::h418648f91f5be3a1+127502049 main+154806636 __libc_start_main+46051972301573 _start+12358494 ``` I didn't find an executor-agnostic watch crate that was well maintained (we already tried postage and async-watch), so decided to implement it our own version. Release Notes: - Fixed a panic that could sometimes occur when the agent performed edits.
This commit is contained in:
parent
95d78ff8d5
commit
019a14bcde
22 changed files with 375 additions and 42 deletions
38
Cargo.lock
generated
38
Cargo.lock
generated
|
@ -60,7 +60,6 @@ dependencies = [
|
|||
"assistant_slash_commands",
|
||||
"assistant_tool",
|
||||
"assistant_tools",
|
||||
"async-watch",
|
||||
"audio",
|
||||
"buffer_diff",
|
||||
"chrono",
|
||||
|
@ -131,6 +130,7 @@ dependencies = [
|
|||
"urlencoding",
|
||||
"util",
|
||||
"uuid",
|
||||
"watch",
|
||||
"workspace",
|
||||
"workspace-hack",
|
||||
"zed_actions",
|
||||
|
@ -631,7 +631,6 @@ name = "assistant_tool"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-watch",
|
||||
"buffer_diff",
|
||||
"clock",
|
||||
"collections",
|
||||
|
@ -653,6 +652,7 @@ dependencies = [
|
|||
"settings",
|
||||
"text",
|
||||
"util",
|
||||
"watch",
|
||||
"workspace",
|
||||
"workspace-hack",
|
||||
"zlog",
|
||||
|
@ -665,7 +665,6 @@ dependencies = [
|
|||
"agent_settings",
|
||||
"anyhow",
|
||||
"assistant_tool",
|
||||
"async-watch",
|
||||
"buffer_diff",
|
||||
"chrono",
|
||||
"client",
|
||||
|
@ -716,6 +715,7 @@ dependencies = [
|
|||
"ui",
|
||||
"unindent",
|
||||
"util",
|
||||
"watch",
|
||||
"web_search",
|
||||
"which 6.0.3",
|
||||
"workspace",
|
||||
|
@ -1074,15 +1074,6 @@ dependencies = [
|
|||
"tungstenite 0.26.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-watch"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a078faf4e27c0c6cc0efb20e5da59dcccc04968ebf2801d8e0b2195124cdcdb2"
|
||||
dependencies = [
|
||||
"event-listener 2.5.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async_zip"
|
||||
version = "0.0.17"
|
||||
|
@ -5013,7 +5004,6 @@ dependencies = [
|
|||
"assistant_tool",
|
||||
"assistant_tools",
|
||||
"async-trait",
|
||||
"async-watch",
|
||||
"buffer_diff",
|
||||
"chrono",
|
||||
"clap",
|
||||
|
@ -5055,6 +5045,7 @@ dependencies = [
|
|||
"unindent",
|
||||
"util",
|
||||
"uuid",
|
||||
"watch",
|
||||
"workspace-hack",
|
||||
"zed_llm_client",
|
||||
]
|
||||
|
@ -8739,7 +8730,6 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"async-watch",
|
||||
"clock",
|
||||
"collections",
|
||||
"ctor",
|
||||
|
@ -8789,6 +8779,7 @@ dependencies = [
|
|||
"unicase",
|
||||
"unindent",
|
||||
"util",
|
||||
"watch",
|
||||
"workspace-hack",
|
||||
"zlog",
|
||||
]
|
||||
|
@ -10147,7 +10138,6 @@ dependencies = [
|
|||
"async-std",
|
||||
"async-tar",
|
||||
"async-trait",
|
||||
"async-watch",
|
||||
"futures 0.3.31",
|
||||
"http_client",
|
||||
"log",
|
||||
|
@ -10157,6 +10147,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"smol",
|
||||
"util",
|
||||
"watch",
|
||||
"which 6.0.3",
|
||||
"workspace-hack",
|
||||
]
|
||||
|
@ -13007,7 +12998,6 @@ dependencies = [
|
|||
"askpass",
|
||||
"assistant_tool",
|
||||
"assistant_tools",
|
||||
"async-watch",
|
||||
"backtrace",
|
||||
"cargo_toml",
|
||||
"chrono",
|
||||
|
@ -13054,6 +13044,7 @@ dependencies = [
|
|||
"toml 0.8.20",
|
||||
"unindent",
|
||||
"util",
|
||||
"watch",
|
||||
"worktree",
|
||||
"zlog",
|
||||
]
|
||||
|
@ -17915,6 +17906,19 @@ dependencies = [
|
|||
"leb128",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "watch"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ctor",
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"parking_lot",
|
||||
"rand 0.8.5",
|
||||
"workspace-hack",
|
||||
"zlog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-backend"
|
||||
version = "0.3.8"
|
||||
|
@ -19726,7 +19730,6 @@ dependencies = [
|
|||
"assistant_context_editor",
|
||||
"assistant_tool",
|
||||
"assistant_tools",
|
||||
"async-watch",
|
||||
"audio",
|
||||
"auto_update",
|
||||
"auto_update_ui",
|
||||
|
@ -19843,6 +19846,7 @@ dependencies = [
|
|||
"uuid",
|
||||
"vim",
|
||||
"vim_mode_setting",
|
||||
"watch",
|
||||
"web_search",
|
||||
"web_search_providers",
|
||||
"welcome",
|
||||
|
|
|
@ -165,6 +165,7 @@ members = [
|
|||
"crates/util_macros",
|
||||
"crates/vim",
|
||||
"crates/vim_mode_setting",
|
||||
"crates/watch",
|
||||
"crates/web_search",
|
||||
"crates/web_search_providers",
|
||||
"crates/welcome",
|
||||
|
@ -373,6 +374,7 @@ util = { path = "crates/util" }
|
|||
util_macros = { path = "crates/util_macros" }
|
||||
vim = { path = "crates/vim" }
|
||||
vim_mode_setting = { path = "crates/vim_mode_setting" }
|
||||
watch = { path = "crates/watch" }
|
||||
web_search = { path = "crates/web_search" }
|
||||
web_search_providers = { path = "crates/web_search_providers" }
|
||||
welcome = { path = "crates/welcome" }
|
||||
|
@ -403,7 +405,6 @@ async-recursion = "1.0.0"
|
|||
async-tar = "0.5.0"
|
||||
async-trait = "0.1"
|
||||
async-tungstenite = "0.29.1"
|
||||
async-watch = "0.3.1"
|
||||
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
|
||||
aws-config = { version = "1.6.1", features = ["behavior-version-latest"] }
|
||||
aws-credential-types = { version = "1.2.2", features = [
|
||||
|
|
|
@ -25,7 +25,6 @@ assistant_context_editor.workspace = true
|
|||
assistant_slash_command.workspace = true
|
||||
assistant_slash_commands.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
async-watch.workspace = true
|
||||
audio.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
chrono.workspace = true
|
||||
|
@ -95,6 +94,7 @@ ui_input.workspace = true
|
|||
urlencoding.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
watch.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
|
|
|
@ -1011,7 +1011,7 @@ impl InlineAssistant {
|
|||
self.update_editor_highlights(&editor, cx);
|
||||
}
|
||||
} else {
|
||||
entry.get().highlight_updates.send(()).ok();
|
||||
entry.get_mut().highlight_updates.send(()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1519,7 +1519,7 @@ impl InlineAssistant {
|
|||
struct EditorInlineAssists {
|
||||
assist_ids: Vec<InlineAssistId>,
|
||||
scroll_lock: Option<InlineAssistScrollLock>,
|
||||
highlight_updates: async_watch::Sender<()>,
|
||||
highlight_updates: watch::Sender<()>,
|
||||
_update_highlights: Task<Result<()>>,
|
||||
_subscriptions: Vec<gpui::Subscription>,
|
||||
}
|
||||
|
@ -1531,7 +1531,7 @@ struct InlineAssistScrollLock {
|
|||
|
||||
impl EditorInlineAssists {
|
||||
fn new(editor: &Entity<Editor>, window: &mut Window, cx: &mut App) -> Self {
|
||||
let (highlight_updates_tx, mut highlight_updates_rx) = async_watch::channel(());
|
||||
let (highlight_updates_tx, mut highlight_updates_rx) = watch::channel(());
|
||||
Self {
|
||||
assist_ids: Vec::new(),
|
||||
scroll_lock: None,
|
||||
|
@ -1689,7 +1689,7 @@ impl InlineAssist {
|
|||
if let Some(editor) = editor.upgrade() {
|
||||
InlineAssistant::update_global(cx, |this, cx| {
|
||||
if let Some(editor_assists) =
|
||||
this.assists_by_editor.get(&editor.downgrade())
|
||||
this.assists_by_editor.get_mut(&editor.downgrade())
|
||||
{
|
||||
editor_assists.highlight_updates.send(()).ok();
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ path = "src/assistant_tool.rs"
|
|||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-watch.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
|
@ -30,6 +29,7 @@ serde.workspace = true
|
|||
serde_json.workspace = true
|
||||
text.workspace = true
|
||||
util.workspace = true
|
||||
watch.workspace = true
|
||||
workspace.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
|
||||
|
|
|
@ -204,7 +204,7 @@ impl ActionLog {
|
|||
git_store.repository_and_path_for_buffer_id(buffer.read(cx).remote_id(), cx)
|
||||
})?;
|
||||
|
||||
let (git_diff_updates_tx, mut git_diff_updates_rx) = async_watch::channel(());
|
||||
let (mut git_diff_updates_tx, mut git_diff_updates_rx) = watch::channel(());
|
||||
let _repo_subscription =
|
||||
if let Some((git_diff, (buffer_repo, _))) = git_diff.as_ref().zip(buffer_repo) {
|
||||
cx.update(|cx| {
|
||||
|
|
|
@ -18,7 +18,6 @@ eval = []
|
|||
agent_settings.workspace = true
|
||||
anyhow.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
async-watch.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
chrono.workspace = true
|
||||
collections.workspace = true
|
||||
|
@ -58,6 +57,7 @@ terminal_view.workspace = true
|
|||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
watch.workspace = true
|
||||
web_search.workspace = true
|
||||
which.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
|
|
|
@ -420,12 +420,12 @@ impl EditAgent {
|
|||
cx: &mut AsyncApp,
|
||||
) -> (
|
||||
Task<Result<(T, Vec<ResolvedOldText>)>>,
|
||||
async_watch::Receiver<Option<Range<usize>>>,
|
||||
watch::Receiver<Option<Range<usize>>>,
|
||||
)
|
||||
where
|
||||
T: 'static + Send + Unpin + Stream<Item = Result<EditParserEvent>>,
|
||||
{
|
||||
let (old_range_tx, old_range_rx) = async_watch::channel(None);
|
||||
let (mut old_range_tx, old_range_rx) = watch::channel(None);
|
||||
let task = cx.background_spawn(async move {
|
||||
let mut matcher = StreamingFuzzyMatcher::new(snapshot);
|
||||
while let Some(edit_event) = edit_events.next().await {
|
||||
|
|
|
@ -24,7 +24,6 @@ anyhow.workspace = true
|
|||
assistant_tool.workspace = true
|
||||
assistant_tools.workspace = true
|
||||
async-trait.workspace = true
|
||||
async-watch.workspace = true
|
||||
buffer_diff.workspace = true
|
||||
chrono.workspace = true
|
||||
clap.workspace = true
|
||||
|
@ -66,5 +65,6 @@ toml.workspace = true
|
|||
unindent.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
watch.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
zed_llm_client.workspace = true
|
||||
|
|
|
@ -385,7 +385,7 @@ pub fn init(cx: &mut App) -> Arc<AgentAppState> {
|
|||
|
||||
extension::init(cx);
|
||||
|
||||
let (tx, rx) = async_watch::channel(None);
|
||||
let (mut tx, rx) = watch::channel(None);
|
||||
cx.observe_global::<SettingsStore>(move |cx| {
|
||||
let settings = &ProjectSettings::get_global(cx).node;
|
||||
let options = NodeBinaryOptions {
|
||||
|
|
|
@ -28,7 +28,6 @@ test-support = [
|
|||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
async-watch.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
ec4rs.workspace = true
|
||||
|
@ -66,6 +65,7 @@ tree-sitter-typescript = { workspace = true, optional = true }
|
|||
tree-sitter.workspace = true
|
||||
unicase = "2.6"
|
||||
util.workspace = true
|
||||
watch.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
diffy = "0.4.2"
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ use crate::{
|
|||
text_diff::text_diff,
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use async_watch as watch;
|
||||
pub use clock::ReplicaId;
|
||||
use clock::{AGENT_REPLICA_ID, Lamport};
|
||||
use collections::HashMap;
|
||||
|
@ -945,7 +944,7 @@ impl Buffer {
|
|||
reparse: None,
|
||||
non_text_state_update_count: 0,
|
||||
sync_parse_timeout: Duration::from_millis(1),
|
||||
parse_status: async_watch::channel(ParseStatus::Idle),
|
||||
parse_status: watch::channel(ParseStatus::Idle),
|
||||
autoindent_requests: Default::default(),
|
||||
pending_autoindent: Default::default(),
|
||||
language: None,
|
||||
|
|
|
@ -18,7 +18,6 @@ test-support = []
|
|||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
async-compression.workspace = true
|
||||
async-watch.workspace = true
|
||||
async-tar.workspace = true
|
||||
async-trait.workspace = true
|
||||
futures.workspace = true
|
||||
|
@ -30,6 +29,7 @@ serde.workspace = true
|
|||
serde_json.workspace = true
|
||||
smol.workspace = true
|
||||
util.workspace = true
|
||||
watch.workspace = true
|
||||
which.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ struct NodeRuntimeState {
|
|||
http: Arc<dyn HttpClient>,
|
||||
instance: Option<Box<dyn NodeRuntimeTrait>>,
|
||||
last_options: Option<NodeBinaryOptions>,
|
||||
options: async_watch::Receiver<Option<NodeBinaryOptions>>,
|
||||
options: watch::Receiver<Option<NodeBinaryOptions>>,
|
||||
shell_env_loaded: Shared<oneshot::Receiver<()>>,
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ impl NodeRuntime {
|
|||
pub fn new(
|
||||
http: Arc<dyn HttpClient>,
|
||||
shell_env_loaded: Option<oneshot::Receiver<()>>,
|
||||
options: async_watch::Receiver<Option<NodeBinaryOptions>>,
|
||||
options: watch::Receiver<Option<NodeBinaryOptions>>,
|
||||
) -> Self {
|
||||
NodeRuntime(Arc::new(Mutex::new(NodeRuntimeState {
|
||||
http,
|
||||
|
@ -60,7 +60,7 @@ impl NodeRuntime {
|
|||
http: Arc::new(http_client::BlockedHttpClient),
|
||||
instance: None,
|
||||
last_options: None,
|
||||
options: async_watch::channel(Some(NodeBinaryOptions::default())).1,
|
||||
options: watch::channel(Some(NodeBinaryOptions::default())).1,
|
||||
shell_env_loaded: oneshot::channel().1.shared(),
|
||||
})))
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ test-support = ["fs/test-support"]
|
|||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
askpass.workspace = true
|
||||
async-watch.workspace = true
|
||||
backtrace = "0.3"
|
||||
chrono.workspace = true
|
||||
clap.workspace = true
|
||||
|
@ -63,6 +62,7 @@ smol.workspace = true
|
|||
sysinfo.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
util.workspace = true
|
||||
watch.workspace = true
|
||||
worktree.workspace = true
|
||||
|
||||
[target.'cfg(not(windows))'.dependencies]
|
||||
|
|
|
@ -756,7 +756,7 @@ fn initialize_settings(
|
|||
session: Arc<ChannelClient>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut App,
|
||||
) -> async_watch::Receiver<Option<NodeBinaryOptions>> {
|
||||
) -> watch::Receiver<Option<NodeBinaryOptions>> {
|
||||
let user_settings_file_rx = watch_config_file(
|
||||
&cx.background_executor(),
|
||||
fs,
|
||||
|
@ -791,7 +791,7 @@ fn initialize_settings(
|
|||
}
|
||||
});
|
||||
|
||||
let (tx, rx) = async_watch::channel(None);
|
||||
let (mut tx, rx) = watch::channel(None);
|
||||
cx.observe_global::<SettingsStore>(move |cx| {
|
||||
let settings = &ProjectSettings::get_global(cx).node;
|
||||
log::info!("Got new node settings: {:?}", settings);
|
||||
|
|
24
crates/watch/Cargo.toml
Normal file
24
crates/watch/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "watch"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "Apache-2.0"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/watch.rs"
|
||||
doctest = true
|
||||
|
||||
[dependencies]
|
||||
parking_lot.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
ctor.workspace = true
|
||||
futures.workspace = true
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
rand.workspace = true
|
||||
zlog.workspace = true
|
1
crates/watch/LICENSE-APACHE
Symbolic link
1
crates/watch/LICENSE-APACHE
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../LICENSE-APACHE
|
25
crates/watch/src/error.rs
Normal file
25
crates/watch/src/error.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
//! Watch error types.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct NoReceiverError;
|
||||
|
||||
impl fmt::Display for NoReceiverError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(fmt, "all receivers were dropped")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for NoReceiverError {}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct NoSenderError;
|
||||
|
||||
impl fmt::Display for NoSenderError {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(fmt, "sender was dropped")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for NoSenderError {}
|
279
crates/watch/src/watch.rs
Normal file
279
crates/watch/src/watch.rs
Normal file
|
@ -0,0 +1,279 @@
|
|||
mod error;
|
||||
|
||||
pub use error::*;
|
||||
use parking_lot::{RwLock, RwLockReadGuard, RwLockUpgradableReadGuard};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
mem,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll, Waker},
|
||||
};
|
||||
|
||||
pub fn channel<T>(value: T) -> (Sender<T>, Receiver<T>) {
|
||||
let state = Arc::new(RwLock::new(State {
|
||||
value,
|
||||
wakers: BTreeMap::new(),
|
||||
next_waker_id: WakerId::default(),
|
||||
version: 0,
|
||||
closed: false,
|
||||
}));
|
||||
|
||||
(
|
||||
Sender {
|
||||
state: state.clone(),
|
||||
},
|
||||
Receiver { state, version: 0 },
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
struct WakerId(usize);
|
||||
|
||||
impl WakerId {
|
||||
fn post_inc(&mut self) -> Self {
|
||||
let id = *self;
|
||||
self.0 = id.0.wrapping_add(1);
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
struct State<T> {
|
||||
value: T,
|
||||
wakers: BTreeMap<WakerId, Waker>,
|
||||
next_waker_id: WakerId,
|
||||
version: usize,
|
||||
closed: bool,
|
||||
}
|
||||
|
||||
pub struct Sender<T> {
|
||||
state: Arc<RwLock<State<T>>>,
|
||||
}
|
||||
|
||||
impl<T> Sender<T> {
|
||||
pub fn receiver(&self) -> Receiver<T> {
|
||||
let version = self.state.read().version;
|
||||
Receiver {
|
||||
state: self.state.clone(),
|
||||
version,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send(&mut self, value: T) -> Result<(), NoReceiverError> {
|
||||
if let Some(state) = Arc::get_mut(&mut self.state) {
|
||||
let state = state.get_mut();
|
||||
state.value = value;
|
||||
debug_assert_eq!(state.wakers.len(), 0);
|
||||
Err(NoReceiverError)
|
||||
} else {
|
||||
let mut state = self.state.write();
|
||||
state.value = value;
|
||||
state.version = state.version.wrapping_add(1);
|
||||
let wakers = mem::take(&mut state.wakers);
|
||||
drop(state);
|
||||
|
||||
for (_, waker) in wakers {
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Sender<T> {
|
||||
fn drop(&mut self) {
|
||||
let mut state = self.state.write();
|
||||
state.closed = true;
|
||||
for (_, waker) in mem::take(&mut state.wakers) {
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Receiver<T> {
|
||||
state: Arc<RwLock<State<T>>>,
|
||||
version: usize,
|
||||
}
|
||||
|
||||
struct Changed<'a, T> {
|
||||
receiver: &'a mut Receiver<T>,
|
||||
pending_waker_id: Option<WakerId>,
|
||||
}
|
||||
|
||||
impl<T> Future for Changed<'_, T> {
|
||||
type Output = Result<(), NoSenderError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||
let this = &mut *self;
|
||||
|
||||
let state = this.receiver.state.upgradable_read();
|
||||
if state.version != this.receiver.version {
|
||||
// The sender produced a new value. Avoid unregistering the pending
|
||||
// waker, because the sender has already done so.
|
||||
this.pending_waker_id = None;
|
||||
this.receiver.version = state.version;
|
||||
Poll::Ready(Ok(()))
|
||||
} else if state.closed {
|
||||
Poll::Ready(Err(NoSenderError))
|
||||
} else {
|
||||
let mut state = RwLockUpgradableReadGuard::upgrade(state);
|
||||
|
||||
// Unregister the pending waker. This should happen automatically
|
||||
// when the waker gets awoken by the sender, but if this future was
|
||||
// polled again without an explicit call to `wake` (e.g., a spurious
|
||||
// wake by the executor), we need to remove it manually.
|
||||
if let Some(pending_waker_id) = this.pending_waker_id.take() {
|
||||
state.wakers.remove(&pending_waker_id);
|
||||
}
|
||||
|
||||
// Register the waker for this future.
|
||||
let waker_id = state.next_waker_id.post_inc();
|
||||
state.wakers.insert(waker_id, cx.waker().clone());
|
||||
this.pending_waker_id = Some(waker_id);
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Changed<'_, T> {
|
||||
fn drop(&mut self) {
|
||||
// If this future gets dropped before the waker has a chance of being
|
||||
// awoken, we need to clear it to avoid a memory leak.
|
||||
if let Some(waker_id) = self.pending_waker_id {
|
||||
let mut state = self.receiver.state.write();
|
||||
state.wakers.remove(&waker_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Receiver<T> {
|
||||
pub fn borrow(&mut self) -> parking_lot::MappedRwLockReadGuard<T> {
|
||||
let state = self.state.read();
|
||||
self.version = state.version;
|
||||
RwLockReadGuard::map(state, |state| &state.value)
|
||||
}
|
||||
|
||||
pub fn changed(&mut self) -> impl Future<Output = Result<(), NoSenderError>> {
|
||||
Changed {
|
||||
receiver: self,
|
||||
pending_waker_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Receiver<T> {
|
||||
pub async fn recv(&mut self) -> Result<T, NoSenderError> {
|
||||
self.changed().await?;
|
||||
Ok(self.borrow().clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use futures::{FutureExt, select_biased};
|
||||
use gpui::{AppContext, TestAppContext};
|
||||
use std::{
|
||||
pin::pin,
|
||||
sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst},
|
||||
};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_basic_watch() {
|
||||
let (mut sender, mut receiver) = channel(0);
|
||||
assert_eq!(sender.send(1), Ok(()));
|
||||
assert_eq!(receiver.recv().await, Ok(1));
|
||||
|
||||
assert_eq!(sender.send(2), Ok(()));
|
||||
assert_eq!(sender.send(3), Ok(()));
|
||||
assert_eq!(receiver.recv().await, Ok(3));
|
||||
|
||||
drop(receiver);
|
||||
assert_eq!(sender.send(4), Err(NoReceiverError));
|
||||
|
||||
let mut receiver = sender.receiver();
|
||||
assert_eq!(sender.send(5), Ok(()));
|
||||
assert_eq!(receiver.recv().await, Ok(5));
|
||||
|
||||
// Ensure `changed` doesn't resolve if we just read the latest value
|
||||
// using `borrow`.
|
||||
assert_eq!(sender.send(6), Ok(()));
|
||||
assert_eq!(*receiver.borrow(), 6);
|
||||
assert_eq!(receiver.changed().now_or_never(), None);
|
||||
|
||||
assert_eq!(sender.send(7), Ok(()));
|
||||
drop(sender);
|
||||
assert_eq!(receiver.recv().await, Ok(7));
|
||||
assert_eq!(receiver.recv().await, Err(NoSenderError));
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 1000)]
|
||||
async fn test_watch_random(cx: &mut TestAppContext) {
|
||||
let next_id = Arc::new(AtomicUsize::new(1));
|
||||
let closed = Arc::new(AtomicBool::new(false));
|
||||
let (mut tx, rx) = channel(0);
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
tasks.push(cx.background_spawn({
|
||||
let executor = cx.executor().clone();
|
||||
let next_id = next_id.clone();
|
||||
let closed = closed.clone();
|
||||
async move {
|
||||
for _ in 0..16 {
|
||||
executor.simulate_random_delay().await;
|
||||
let id = next_id.fetch_add(1, SeqCst);
|
||||
zlog::info!("sending {}", id);
|
||||
tx.send(id).ok();
|
||||
}
|
||||
closed.store(true, SeqCst);
|
||||
}
|
||||
}));
|
||||
|
||||
for receiver_id in 0..16 {
|
||||
let executor = cx.executor().clone();
|
||||
let next_id = next_id.clone();
|
||||
let closed = closed.clone();
|
||||
let mut rx = rx.clone();
|
||||
let mut prev_observed_value = *rx.borrow();
|
||||
tasks.push(cx.background_spawn(async move {
|
||||
for _ in 0..16 {
|
||||
executor.simulate_random_delay().await;
|
||||
|
||||
zlog::info!("{}: receiving", receiver_id);
|
||||
let mut timeout = executor.simulate_random_delay().fuse();
|
||||
let mut recv = pin!(rx.recv().fuse());
|
||||
select_biased! {
|
||||
_ = timeout => {
|
||||
zlog::info!("{}: dropping recv future", receiver_id);
|
||||
}
|
||||
result = recv => {
|
||||
match result {
|
||||
Ok(value) => {
|
||||
zlog::info!("{}: received {}", receiver_id, value);
|
||||
assert_eq!(value, next_id.load(SeqCst) - 1);
|
||||
assert_ne!(value, prev_observed_value);
|
||||
prev_observed_value = value;
|
||||
}
|
||||
Err(NoSenderError) => {
|
||||
zlog::info!("{}: closed", receiver_id);
|
||||
assert!(closed.load(SeqCst));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
futures::future::join_all(tasks).await;
|
||||
}
|
||||
|
||||
#[ctor::ctor]
|
||||
fn init_logger() {
|
||||
zlog::init_test();
|
||||
}
|
||||
}
|
|
@ -28,7 +28,6 @@ assets.workspace = true
|
|||
assistant_context_editor.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
assistant_tools.workspace = true
|
||||
async-watch.workspace = true
|
||||
audio.workspace = true
|
||||
auto_update.workspace = true
|
||||
auto_update_ui.workspace = true
|
||||
|
@ -142,6 +141,7 @@ util.workspace = true
|
|||
uuid.workspace = true
|
||||
vim.workspace = true
|
||||
vim_mode_setting.workspace = true
|
||||
watch.workspace = true
|
||||
web_search.workspace = true
|
||||
web_search_providers.workspace = true
|
||||
welcome.workspace = true
|
||||
|
|
|
@ -412,7 +412,7 @@ Error: Running Zed as root or via sudo is unsupported.
|
|||
let mut languages = LanguageRegistry::new(cx.background_executor().clone());
|
||||
languages.set_language_server_download_dir(paths::languages_dir().clone());
|
||||
let languages = Arc::new(languages);
|
||||
let (tx, rx) = async_watch::channel(None);
|
||||
let (mut tx, rx) = watch::channel(None);
|
||||
cx.observe_global::<SettingsStore>(move |cx| {
|
||||
let settings = &ProjectSettings::get_global(cx).node;
|
||||
let options = NodeBinaryOptions {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue