gpui_tokio_bridge/
lib.rs

1//! Tokio bridge for GPUI - allows running tokio async tasks within GPUI context.
2//!
3//! Based on code from Zed Industries:
4//! https://github.com/zed-industries/zed/blob/af6385a29355dd2859e9751ebd76e418dd6a249a/crates/gpui_tokio/src/gpui_tokio.rs
5
6use std::future::Future;
7
8use gpui::{App, AppContext, Global, Task};
9
10pub use tokio::task::JoinError;
11
12/// Initializes the Tokio wrapper using a new Tokio runtime with 2 worker threads.
13pub fn init(cx: &mut App) {
14    let runtime = tokio::runtime::Builder::new_multi_thread()
15        .worker_threads(2)
16        .enable_all()
17        .build()
18        .expect("Failed to initialize Tokio");
19
20    cx.set_global(GlobalTokio::new(RuntimeHolder::Owned(runtime)));
21}
22
23enum RuntimeHolder {
24    Owned(tokio::runtime::Runtime),
25    #[allow(dead_code)]
26    Shared(tokio::runtime::Handle),
27}
28
29impl RuntimeHolder {
30    pub fn handle(&self) -> &tokio::runtime::Handle {
31        match self {
32            RuntimeHolder::Owned(runtime) => runtime.handle(),
33            RuntimeHolder::Shared(handle) => handle,
34        }
35    }
36}
37
38struct GlobalTokio {
39    runtime: RuntimeHolder,
40}
41
42impl Global for GlobalTokio {}
43
44impl GlobalTokio {
45    fn new(runtime: RuntimeHolder) -> Self {
46        Self { runtime }
47    }
48}
49
50/// Helper to abort tokio task when dropped
51struct AbortOnDrop(tokio::task::AbortHandle);
52
53impl Drop for AbortOnDrop {
54    fn drop(&mut self) {
55        self.0.abort();
56    }
57}
58
59pub struct Tokio;
60
61impl Tokio {
62    /// Spawns the given future on Tokio's thread pool, and returns it via a GPUI task.
63    /// Note that the Tokio task will be cancelled if the GPUI task is dropped.
64    pub fn spawn<T, Fut, R>(cx: &mut gpui::Context<'_, T>, f: Fut) -> Task<Result<R, JoinError>>
65    where
66        Fut: Future<Output = R> + Send + 'static,
67        R: Send + 'static,
68    {
69        let tokio = cx.global::<GlobalTokio>();
70        let join_handle = tokio.runtime.handle().spawn(f);
71        let abort_guard = AbortOnDrop(join_handle.abort_handle());
72        cx.background_spawn(async move {
73            let result = join_handle.await;
74            drop(abort_guard);
75            result
76        })
77    }
78}