From bd213340133f23add7d5b1d4bc250d1880b6ff95 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 29 Jan 2025 12:53:16 -0800 Subject: [PATCH] Add a crate for spawning tokio tasks in Zed (#23857) Part of https://github.com/zed-industries/zed/pull/21092 As we're already depending on and using `tokio` to run `reqwest`, I've added a crate to make running tokio futures more convenient. This should unblock the Bedrock Cloud Model provider PR. Note that since the `gpui_tokio` code is nearly trivial glue and I expect that it will be useful for the nascent GPUI ecosystem, I've elected to license it under Apache 2, like GPUI itself, instead of our normal GPL license for Zed code. Release Notes: - N/A --- Cargo.lock | 11 ++++++ Cargo.toml | 10 +++--- crates/gpui_tokio/Cargo.toml | 18 ++++++++++ crates/gpui_tokio/LICENSE-APACHE | 1 + crates/gpui_tokio/src/gpui_tokio.rs | 55 +++++++++++++++++++++++++++++ crates/remote_server/Cargo.toml | 1 + crates/remote_server/src/unix.rs | 27 ++++++++------ crates/zed/Cargo.toml | 1 + crates/zed/src/main.rs | 9 +++-- 9 files changed, 116 insertions(+), 17 deletions(-) create mode 100644 crates/gpui_tokio/Cargo.toml create mode 120000 crates/gpui_tokio/LICENSE-APACHE create mode 100644 crates/gpui_tokio/src/gpui_tokio.rs diff --git a/Cargo.lock b/Cargo.lock index f892f66da321ab26b2c8021b346c3d4ccf43953b..c12992e614ee427adf902b2288a0a971558b15ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5491,6 +5491,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "gpui_tokio" +version = "0.1.0" +dependencies = [ + "gpui", + "tokio", + "util", +] + [[package]] name = "grid" version = "0.13.0" @@ -10746,6 +10755,7 @@ dependencies = [ "git", "git_hosting_providers", "gpui", + "gpui_tokio", "http_client", "language", "language_extension", @@ -16439,6 +16449,7 @@ dependencies = [ "git_ui", "go_to_line", "gpui", + "gpui_tokio", "http_client", "image_viewer", "inline_completion_button", diff --git a/Cargo.toml b/Cargo.toml index fa79b66f510a63906a1607d6febe63125dccf545..9b1dd365574a9244d06a685f9004641c03c89fa8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,6 @@ resolver = "2" members = [ "crates/activity_indicator", - "crates/zed_predict_tos", "crates/anthropic", "crates/assets", "crates/assistant", @@ -31,8 +30,8 @@ members = [ "crates/context_server_settings", "crates/copilot", "crates/db", - "crates/diagnostics", "crates/deepseek", + "crates/diagnostics", "crates/docs_preprocessor", "crates/editor", "crates/evals", @@ -51,10 +50,12 @@ members = [ "crates/fuzzy", "crates/git", "crates/git_hosting_providers", + "crates/git_ui", "crates/go_to_line", "crates/google_ai", "crates/gpui", "crates/gpui_macros", + "crates/gpui_tokio", "crates/html_to_markdown", "crates/http_client", "crates/image_viewer", @@ -103,6 +104,7 @@ members = [ "crates/remote_server", "crates/repl", "crates/reqwest_client", + "crates/reqwest_client", "crates/rich_text", "crates/rope", "crates/rpc", @@ -141,7 +143,6 @@ members = [ "crates/ui", "crates/ui_input", "crates/ui_macros", - "crates/reqwest_client", "crates/util", "crates/vcs_menu", "crates/vim", @@ -151,8 +152,8 @@ members = [ "crates/worktree", "crates/zed", "crates/zed_actions", + "crates/zed_predict_tos", "crates/zeta", - "crates/git_ui", # # Extensions @@ -253,6 +254,7 @@ gpui = { path = "crates/gpui", default-features = false, features = [ "http_client", ] } gpui_macros = { path = "crates/gpui_macros" } +gpui_tokio = { path = "crates/gpui_tokio" } html_to_markdown = { path = "crates/html_to_markdown" } http_client = { path = "crates/http_client" } image_viewer = { path = "crates/image_viewer" } diff --git a/crates/gpui_tokio/Cargo.toml b/crates/gpui_tokio/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..940548f1d6eb4c6bbab50bb4a53c5dcedc518ddc --- /dev/null +++ b/crates/gpui_tokio/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "gpui_tokio" +version = "0.1.0" +edition.workspace = true +publish.workspace = true +license = "Apache-2.0" + +[lints] +workspace = true + +[lib] +path = "src/gpui_tokio.rs" +doctest = false + +[dependencies] +util.workspace = true +gpui.workspace = true +tokio = { workspace = true, features = ["rt", "rt-multi-thread"] } diff --git a/crates/gpui_tokio/LICENSE-APACHE b/crates/gpui_tokio/LICENSE-APACHE new file mode 120000 index 0000000000000000000000000000000000000000..1cd601d0a3affae83854be02a0afdec3b7a9ec4d --- /dev/null +++ b/crates/gpui_tokio/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE \ No newline at end of file diff --git a/crates/gpui_tokio/src/gpui_tokio.rs b/crates/gpui_tokio/src/gpui_tokio.rs new file mode 100644 index 0000000000000000000000000000000000000000..ecc17fa3ec6d262639206895ecc0170a994f9e98 --- /dev/null +++ b/crates/gpui_tokio/src/gpui_tokio.rs @@ -0,0 +1,55 @@ +use std::future::Future; + +use gpui::{App, Global, ReadGlobal, Task}; +use tokio::task::JoinError; +use util::defer; + +pub fn init(cx: &mut App) { + cx.set_global(GlobalTokio::new()); +} + +struct GlobalTokio { + runtime: tokio::runtime::Runtime, +} + +impl Global for GlobalTokio {} + +impl GlobalTokio { + fn new() -> Self { + let runtime = tokio::runtime::Builder::new_multi_thread() + // Since we now have two executors, let's try to keep our footprint small + .worker_threads(2) + .enable_all() + .build() + .expect("Failed to initialize Tokio"); + + Self { runtime } + } +} + +pub struct Tokio {} + +impl Tokio { + /// Spawns the given future on Tokio's thread pool, and returns it via a GPUI task + /// Note that the Tokio task will be cancelled if the GPUI task is dropped + pub fn spawn(cx: &mut App, f: Fut) -> Task> + where + Fut: Future + Send + 'static, + R: Send + 'static, + { + let join_handle = GlobalTokio::global(cx).runtime.spawn(f); + let abort_handle = join_handle.abort_handle(); + let cancel = defer(move || { + abort_handle.abort(); + }); + cx.background_executor().spawn(async move { + let result = join_handle.await; + drop(cancel); + result + }) + } + + pub fn handle(cx: &mut App) -> tokio::runtime::Handle { + GlobalTokio::global(cx).runtime.handle().clone() + } +} diff --git a/crates/remote_server/Cargo.toml b/crates/remote_server/Cargo.toml index a9baa0a6ad1efe1ddeef7ac9ff44e5952841f75d..f2238d91fa2d3956a469cdf3d2acbc79cfba77a7 100644 --- a/crates/remote_server/Cargo.toml +++ b/crates/remote_server/Cargo.toml @@ -36,6 +36,7 @@ futures.workspace = true git.workspace = true git_hosting_providers.workspace = true gpui.workspace = true +gpui_tokio.workspace = true http_client.workspace = true language.workspace = true language_extension.workspace = true diff --git a/crates/remote_server/src/unix.rs b/crates/remote_server/src/unix.rs index 3a856c2ecb79ec639a52d69841a0877b33629341..10ac263cc4e06818744ab1eee7b7c36d365a9b7d 100644 --- a/crates/remote_server/src/unix.rs +++ b/crates/remote_server/src/unix.rs @@ -9,6 +9,7 @@ use futures::channel::mpsc; use futures::{select, select_biased, AsyncRead, AsyncWrite, AsyncWriteExt, FutureExt, SinkExt}; use git::GitHostingProviderRegistry; use gpui::{App, AppContext as _, Context, Entity, SemanticVersion, UpdateGlobal as _}; +use gpui_tokio::Tokio; use http_client::{read_proxy_from_env, Uri}; use language::LanguageRegistry; use node_runtime::{NodeBinaryOptions, NodeRuntime}; @@ -425,6 +426,7 @@ pub fn execute_run( settings::init(cx); let app_version = AppVersion::init(env!("ZED_PKG_VERSION")); release_channel::init(app_version, cx); + gpui_tokio::init(cx); HeadlessProject::init(cx); @@ -445,18 +447,21 @@ pub fn execute_run( let proxy_url = read_proxy_settings(cx); - let http_client = Arc::new( - ReqwestClient::proxy_and_user_agent( - proxy_url, - &format!( - "Zed-Server/{} ({}; {})", - env!("CARGO_PKG_VERSION"), - std::env::consts::OS, - std::env::consts::ARCH - ), + let http_client = { + let _guard = Tokio::handle(cx).enter(); + Arc::new( + ReqwestClient::proxy_and_user_agent( + proxy_url, + &format!( + "Zed-Server/{} ({}; {})", + env!("CARGO_PKG_VERSION"), + std::env::consts::OS, + std::env::consts::ARCH + ), + ) + .expect("Could not start HTTP client"), ) - .expect("Could not start HTTP client"), - ); + }; let node_runtime = NodeRuntime::new(http_client.clone(), node_settings_rx); diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 5e2befe108796f5ec798084bf8168462bcb885d7..7e53c12ae14bf6962101fd499818ee0612f5661c 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -59,6 +59,7 @@ git_ui.workspace = true git_hosting_providers.workspace = true go_to_line.workspace = true gpui = { workspace = true, features = ["wayland", "x11", "font-kit"] } +gpui_tokio.workspace = true http_client.workspace = true image_viewer.workspace = true inline_completion_button.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 8fc4cef8de8d20fc9270c95d3b4d36d4dff455f3..35103fbba51221fd089c41b67f2f26962c87ba05 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -20,6 +20,7 @@ use futures::{future, StreamExt}; use git::GitHostingProviderRegistry; use gpui::{App, AppContext as _, Application, AsyncApp, UpdateGlobal as _}; +use gpui_tokio::Tokio; use http_client::{read_proxy_from_env, Uri}; use language::LanguageRegistry; use log::LevelFilter; @@ -279,6 +280,7 @@ fn main() { app.run(move |cx| { release_channel::init(app_version, cx); + gpui_tokio::init(cx); if let Some(build_sha) = option_env!("ZED_COMMIT_SHA") { AppCommitSha::set_global(AppCommitSha(build_sha.into()), cx); } @@ -302,8 +304,11 @@ fn main() { .ok() }) .or_else(read_proxy_from_env); - let http = ReqwestClient::proxy_and_user_agent(proxy_url, &user_agent) - .expect("could not start HTTP client"); + let http = { + let _guard = Tokio::handle(cx).enter(); + ReqwestClient::proxy_and_user_agent(proxy_url, &user_agent) + .expect("could not start HTTP client") + }; cx.set_http_client(Arc::new(http)); ::set_global(fs.clone(), cx);