Make tasks inherit their callers priority (#46179)

Yara 🏳️‍⚧️ , Lukas Wirth , and Cole Miller created

Additionally this extracts more functionality into the RunnableVariant which is renamed to GpuiRunnable. 

Release Notes:

- N/A

---------

Co-authored-by: Lukas Wirth <lukas@zed.dev>
Co-authored-by: Cole Miller <cole@zed.dev>

Change summary

crates/git_ui/src/git_panel.rs                    |   7 
crates/gpui/src/executor.rs                       |  46 ++++--
crates/gpui/src/platform.rs                       |  75 +++++++++-
crates/gpui/src/platform/linux/dispatcher.rs      |  98 ++-----------
crates/gpui/src/platform/linux/headless/client.rs |   5 
crates/gpui/src/platform/linux/platform.rs        |  10 
crates/gpui/src/platform/linux/wayland/client.rs  |  41 -----
crates/gpui/src/platform/linux/x11/client.rs      |  34 ----
crates/gpui/src/platform/mac/dispatcher.rs        | 117 +++-------------
crates/gpui/src/platform/test/dispatcher.rs       |  33 +---
crates/gpui/src/platform/windows/dispatcher.rs    |  81 +++--------
crates/gpui/src/platform/windows/events.rs        |   2 
crates/gpui/src/platform/windows/platform.rs      |  13 -
crates/gpui/src/platform/windows/window.rs        |   4 
crates/repl/src/repl.rs                           |   6 
15 files changed, 207 insertions(+), 365 deletions(-)

Detailed changes

crates/git_ui/src/git_panel.rs 🔗

@@ -80,6 +80,7 @@ use workspace::{
     dock::{DockPosition, Panel, PanelEvent},
     notifications::{DetachAndPromptErr, ErrorMessagePrompt, NotificationId, NotifyResultExt},
 };
+use ztracing::instrument;
 actions!(
     git_panel,
     [
@@ -1197,6 +1198,7 @@ impl GitPanel {
         self.selected_entry.and_then(|i| self.entries.get(i))
     }
 
+    #[instrument(skip_all)]
     fn open_diff(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
         maybe!({
             let entry = self.entries.get(self.selected_entry?)?.status_entry()?;
@@ -1247,6 +1249,7 @@ impl GitPanel {
         });
     }
 
+    #[instrument(skip_all)]
     fn open_file(
         &mut self,
         _: &menu::SecondaryConfirm,
@@ -5082,9 +5085,9 @@ impl GitPanel {
                     this.selected_entry = Some(ix);
                     cx.notify();
                     if event.modifiers().secondary() {
-                        this.open_file(&Default::default(), window, cx)
+                        this.open_file(&Default::default(), window, cx) // here?
                     } else {
-                        this.open_diff(&Default::default(), window, cx);
+                        this.open_diff(&Default::default(), window, cx); // here?
                         this.focus_handle.focus(window, cx);
                     }
                 })

crates/gpui/src/executor.rs 🔗

@@ -1,9 +1,10 @@
-use crate::{App, PlatformDispatcher, RunnableMeta, RunnableVariant, TaskTiming, profiler};
+use crate::{App, GpuiRunnable, PlatformDispatcher, RunnableMeta, TaskTiming, profiler};
 use async_task::Runnable;
 use futures::channel::mpsc;
 use parking_lot::{Condvar, Mutex};
 use smol::prelude::*;
 use std::{
+    cell::Cell,
     fmt::Debug,
     marker::PhantomData,
     mem::{self, ManuallyDrop},
@@ -81,7 +82,21 @@ pub enum Priority {
     Low,
 }
 
+thread_local! {
+static CURRENT_TASKS_PRIORITY: Cell<Priority> = const { Cell::new(Priority::Medium) }; }
+
 impl Priority {
+    /// Sets the priority any spawn call from the runnable about
+    /// to be run will use
+    pub(crate) fn set_as_default_for_spawns(&self) {
+        CURRENT_TASKS_PRIORITY.set(*self);
+    }
+
+    /// Returns the priority from the currently running task
+    pub fn inherit() -> Self {
+        CURRENT_TASKS_PRIORITY.get()
+    }
+
     #[allow(dead_code)]
     pub(crate) const fn probability(&self) -> u32 {
         match self {
@@ -329,19 +344,14 @@ impl BackgroundExecutor {
                 .metadata(RunnableMeta {
                     location,
                     app: None,
+                    priority: Priority::inherit(),
                 })
                 .spawn_unchecked(
                     move |_| async {
                         let _notify_guard = NotifyOnDrop(pair);
                         future.await
                     },
-                    move |runnable| {
-                        dispatcher.dispatch(
-                            RunnableVariant::Meta(runnable),
-                            None,
-                            Priority::default(),
-                        )
-                    },
+                    move |runnable| dispatcher.dispatch(GpuiRunnable::GpuiSpawned(runnable), None),
                 )
         };
         runnable.schedule();
@@ -387,6 +397,7 @@ impl BackgroundExecutor {
                         };
                         profiler::add_task_timing(timing);
 
+                        Priority::Realtime(realtime).set_as_default_for_spawns();
                         runnable.run();
 
                         let end = Instant::now();
@@ -399,6 +410,7 @@ impl BackgroundExecutor {
             async_task::Builder::new()
                 .metadata(RunnableMeta {
                     location,
+                    priority,
                     app: None,
                 })
                 .spawn(
@@ -412,13 +424,12 @@ impl BackgroundExecutor {
             async_task::Builder::new()
                 .metadata(RunnableMeta {
                     location,
+                    priority,
                     app: None,
                 })
                 .spawn(
                     move |_| future,
-                    move |runnable| {
-                        dispatcher.dispatch(RunnableVariant::Meta(runnable), label, priority)
-                    },
+                    move |runnable| dispatcher.dispatch(GpuiRunnable::GpuiSpawned(runnable), label),
                 )
         };
 
@@ -674,11 +685,14 @@ impl BackgroundExecutor {
         let (runnable, task) = async_task::Builder::new()
             .metadata(RunnableMeta {
                 location,
+                priority: Priority::inherit(),
                 app: None,
             })
             .spawn(move |_| async move {}, {
                 let dispatcher = self.dispatcher.clone();
-                move |runnable| dispatcher.dispatch_after(duration, RunnableVariant::Meta(runnable))
+                move |runnable| {
+                    dispatcher.dispatch_after(duration, GpuiRunnable::GpuiSpawned(runnable))
+                }
             });
         runnable.schedule();
         Task(TaskState::Spawned(task))
@@ -785,7 +799,10 @@ impl ForegroundExecutor {
         }
     }
 
-    /// Enqueues the given Task to run on the main thread at some point in the future.
+    /// Enqueues the given Task to run on the main thread at some point in the
+    /// future. This inherits the priority of the caller. Use
+    /// [`spawn_with_priority`](Self::spawn_with_priority) if you want to
+    /// overwrite that.
     #[track_caller]
     pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
     where
@@ -831,10 +848,11 @@ impl ForegroundExecutor {
             let (runnable, task) = spawn_local_with_source_location(
                 future,
                 move |runnable| {
-                    dispatcher.dispatch_on_main_thread(RunnableVariant::Meta(runnable), priority)
+                    dispatcher.dispatch_on_main_thread(GpuiRunnable::GpuiSpawned(runnable))
                 },
                 RunnableMeta {
                     location,
+                    priority,
                     app: Some(app),
                 },
             );

crates/gpui/src/platform.rs 🔗

@@ -590,7 +590,8 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
 /// be considered part of our public API.
 #[doc(hidden)]
 pub struct RunnableMeta {
-    /// Location of the runnable
+    pub priority: Priority,
+    /// Location of the runnable, only set for futures we spawn
     pub location: &'static core::panic::Location<'static>,
     /// Weak reference to check if the app is still alive before running this task
     pub app: Option<std::sync::Weak<()>>,
@@ -599,6 +600,7 @@ pub struct RunnableMeta {
 impl std::fmt::Debug for RunnableMeta {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("RunnableMeta")
+            .field("priority", &self.priority)
             .field("location", &self.location)
             .field("app_alive", &self.is_app_alive())
             .finish()
@@ -615,10 +617,69 @@ impl RunnableMeta {
     }
 }
 
+/// Both of these need meta for priority
 #[doc(hidden)]
-pub enum RunnableVariant {
-    Meta(Runnable<RunnableMeta>),
-    Compat(Runnable),
+pub enum GpuiRunnable {
+    /// Spawned by us, we set useful metadata for profiling and scheduling.
+    /// Yay we have nice things!
+    GpuiSpawned(Runnable<RunnableMeta>),
+    /// Spawned by a dependency through runtimelib. We only have the
+    /// runnable ('task'). No access to metadata.
+    DependencySpawned(Runnable<()>),
+}
+
+impl GpuiRunnable {
+    fn run_and_profile(self) -> Instant {
+        if self.app_dropped() {
+            // optimizer will cut it out if it doesnt it does not really matter
+            // cause we are closing the app anyway.
+            return Instant::now();
+        }
+
+        let mut timing = TaskTiming {
+            // use a placeholder location if we dont have one
+            location: self.location().unwrap_or(core::panic::Location::caller()),
+            start: Instant::now(),
+            end: None,
+        };
+
+        crate::profiler::add_task_timing(timing);
+        self.run_unprofiled(); // surrounded by profiling so its ok
+        timing.end = Some(Instant::now());
+        crate::profiler::add_task_timing(timing);
+        timing.start
+    }
+
+    fn app_dropped(&self) -> bool {
+        match self {
+            GpuiRunnable::GpuiSpawned(runnable) => !runnable.metadata().is_app_alive(),
+            GpuiRunnable::DependencySpawned(_) => false,
+        }
+    }
+
+    fn location(&self) -> Option<&'static core::panic::Location<'static>> {
+        match self {
+            GpuiRunnable::GpuiSpawned(runnable) => runnable.metadata().location.into(),
+            GpuiRunnable::DependencySpawned(_) => None,
+        }
+    }
+
+    /// ONLY use for tests or headless client.
+    /// ideally everything should be profiled
+    fn run_unprofiled(self) {
+        self.priority().set_as_default_for_spawns();
+        match self {
+            GpuiRunnable::GpuiSpawned(r) => r.run(),
+            GpuiRunnable::DependencySpawned(r) => r.run(),
+        };
+    }
+
+    fn priority(&self) -> Priority {
+        match self {
+            GpuiRunnable::GpuiSpawned(r) => r.metadata().priority,
+            GpuiRunnable::DependencySpawned(_) => Priority::Medium,
+        }
+    }
 }
 
 /// This type is public so that our test macro can generate and use it, but it should not
@@ -628,9 +689,9 @@ pub trait PlatformDispatcher: Send + Sync {
     fn get_all_timings(&self) -> Vec<ThreadTaskTimings>;
     fn get_current_thread_timings(&self) -> Vec<TaskTiming>;
     fn is_main_thread(&self) -> bool;
-    fn dispatch(&self, runnable: RunnableVariant, label: Option<TaskLabel>, priority: Priority);
-    fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority);
-    fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant);
+    fn dispatch(&self, runnable: GpuiRunnable, label: Option<TaskLabel>);
+    fn dispatch_on_main_thread(&self, runnable: GpuiRunnable);
+    fn dispatch_after(&self, duration: Duration, runnable: GpuiRunnable);
     fn spawn_realtime(&self, priority: RealtimePriority, f: Box<dyn FnOnce() + Send>);
 
     fn now(&self) -> Instant {

crates/gpui/src/platform/linux/dispatcher.rs 🔗

@@ -5,27 +5,22 @@ use calloop::{
 };
 use util::ResultExt;
 
-use std::{
-    mem::MaybeUninit,
-    thread,
-    time::{Duration, Instant},
-};
+use std::{mem::MaybeUninit, thread, time::Duration};
 
 use crate::{
-    GLOBAL_THREAD_TIMINGS, PlatformDispatcher, Priority, PriorityQueueReceiver,
-    PriorityQueueSender, RealtimePriority, RunnableVariant, THREAD_TIMINGS, TaskLabel, TaskTiming,
-    ThreadTaskTimings, profiler,
+    GLOBAL_THREAD_TIMINGS, GpuiRunnable, PlatformDispatcher, Priority, PriorityQueueReceiver,
+    PriorityQueueSender, RealtimePriority, THREAD_TIMINGS, TaskLabel, ThreadTaskTimings,
 };
 
 struct TimerAfter {
     duration: Duration,
-    runnable: RunnableVariant,
+    runnable: GpuiRunnable,
 }
 
 pub(crate) struct LinuxDispatcher {
-    main_sender: PriorityQueueCalloopSender<RunnableVariant>,
+    main_sender: PriorityQueueCalloopSender<GpuiRunnable>,
     timer_sender: Sender<TimerAfter>,
-    background_sender: PriorityQueueSender<RunnableVariant>,
+    background_sender: PriorityQueueSender<GpuiRunnable>,
     _background_threads: Vec<thread::JoinHandle<()>>,
     main_thread_id: thread::ThreadId,
 }
@@ -33,7 +28,7 @@ pub(crate) struct LinuxDispatcher {
 const MIN_THREADS: usize = 2;
 
 impl LinuxDispatcher {
-    pub fn new(main_sender: PriorityQueueCalloopSender<RunnableVariant>) -> Self {
+    pub fn new(main_sender: PriorityQueueCalloopSender<GpuiRunnable>) -> Self {
         let (background_sender, background_receiver) = PriorityQueueReceiver::new();
         let thread_count =
             std::thread::available_parallelism().map_or(MIN_THREADS, |i| i.get().max(MIN_THREADS));
@@ -42,48 +37,16 @@ impl LinuxDispatcher {
         // executor
         let mut background_threads = (0..thread_count)
             .map(|i| {
-                let mut receiver = background_receiver.clone();
+                let mut receiver: PriorityQueueReceiver<GpuiRunnable> = background_receiver.clone();
                 std::thread::Builder::new()
                     .name(format!("Worker-{i}"))
                     .spawn(move || {
                         for runnable in receiver.iter() {
-                            let start = Instant::now();
-
-                            let mut location = match runnable {
-                                RunnableVariant::Meta(runnable) => {
-                                    let location = runnable.metadata().location;
-                                    let timing = TaskTiming {
-                                        location,
-                                        start,
-                                        end: None,
-                                    };
-                                    profiler::add_task_timing(timing);
-
-                                    runnable.run();
-                                    timing
-                                }
-                                RunnableVariant::Compat(runnable) => {
-                                    let location = core::panic::Location::caller();
-                                    let timing = TaskTiming {
-                                        location,
-                                        start,
-                                        end: None,
-                                    };
-                                    profiler::add_task_timing(timing);
-
-                                    runnable.run();
-                                    timing
-                                }
-                            };
-
-                            let end = Instant::now();
-                            location.end = Some(end);
-                            profiler::add_task_timing(location);
-
+                            let started = runnable.run_and_profile();
                             log::trace!(
                                 "background thread {}: ran runnable. took: {:?}",
                                 i,
-                                start.elapsed()
+                                started.elapsed()
                             );
                         }
                     })
@@ -110,36 +73,7 @@ impl LinuxDispatcher {
                                     calloop::timer::Timer::from_duration(timer.duration),
                                     move |_, _, _| {
                                         if let Some(runnable) = runnable.take() {
-                                            let start = Instant::now();
-                                            let mut timing = match runnable {
-                                                RunnableVariant::Meta(runnable) => {
-                                                    let location = runnable.metadata().location;
-                                                    let timing = TaskTiming {
-                                                        location,
-                                                        start,
-                                                        end: None,
-                                                    };
-                                                    profiler::add_task_timing(timing);
-
-                                                    runnable.run();
-                                                    timing
-                                                }
-                                                RunnableVariant::Compat(runnable) => {
-                                                    let timing = TaskTiming {
-                                                        location: core::panic::Location::caller(),
-                                                        start,
-                                                        end: None,
-                                                    };
-                                                    profiler::add_task_timing(timing);
-
-                                                    runnable.run();
-                                                    timing
-                                                }
-                                            };
-                                            let end = Instant::now();
-
-                                            timing.end = Some(end);
-                                            profiler::add_task_timing(timing);
+                                            runnable.run_and_profile();
                                         }
                                         TimeoutAction::Drop
                                     },
@@ -189,15 +123,15 @@ impl PlatformDispatcher for LinuxDispatcher {
         thread::current().id() == self.main_thread_id
     }
 
-    fn dispatch(&self, runnable: RunnableVariant, _: Option<TaskLabel>, priority: Priority) {
+    fn dispatch(&self, runnable: GpuiRunnable, _: Option<TaskLabel>) {
         self.background_sender
-            .send(priority, runnable)
+            .send(runnable.priority(), runnable)
             .unwrap_or_else(|_| panic!("blocking sender returned without value"));
     }
 
-    fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority) {
+    fn dispatch_on_main_thread(&self, runnable: GpuiRunnable) {
         self.main_sender
-            .send(priority, runnable)
+            .send(runnable.priority(), runnable)
             .unwrap_or_else(|runnable| {
                 // NOTE: Runnable may wrap a Future that is !Send.
                 //
@@ -211,7 +145,7 @@ impl PlatformDispatcher for LinuxDispatcher {
             });
     }
 
-    fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant) {
+    fn dispatch_after(&self, duration: Duration, runnable: GpuiRunnable) {
         self.timer_sender
             .send(TimerAfter { duration, runnable })
             .ok();

crates/gpui/src/platform/linux/headless/client.rs 🔗

@@ -31,10 +31,7 @@ impl HeadlessClient {
         handle
             .insert_source(main_receiver, |event, _, _: &mut HeadlessClient| {
                 if let calloop::channel::Event::Msg(runnable) = event {
-                    match runnable {
-                        crate::RunnableVariant::Meta(runnable) => runnable.run(),
-                        crate::RunnableVariant::Compat(runnable) => runnable.run(),
-                    };
+                    runnable.run_unprofiled();
                 }
             })
             .ok();

crates/gpui/src/platform/linux/platform.rs 🔗

@@ -23,10 +23,10 @@ use xkbcommon::xkb::{self, Keycode, Keysym, State};
 
 use crate::{
     Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
-    ForegroundExecutor, Keymap, LinuxDispatcher, Menu, MenuItem, OwnedMenu, PathPromptOptions,
-    Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper,
-    PlatformTextSystem, PlatformWindow, Point, PriorityQueueCalloopReceiver, Result,
-    RunnableVariant, Task, WindowAppearance, WindowParams, px,
+    ForegroundExecutor, GpuiRunnable, Keymap, LinuxDispatcher, Menu, MenuItem, OwnedMenu,
+    PathPromptOptions, Pixels, Platform, PlatformDisplay, PlatformKeyboardLayout,
+    PlatformKeyboardMapper, PlatformTextSystem, PlatformWindow, Point,
+    PriorityQueueCalloopReceiver, Result, Task, WindowAppearance, WindowParams, px,
 };
 
 #[cfg(any(feature = "wayland", feature = "x11"))]
@@ -152,7 +152,7 @@ impl LinuxCommon {
     pub fn new(
         signal: LoopSignal,
         liveness: std::sync::Weak<()>,
-    ) -> (Self, PriorityQueueCalloopReceiver<RunnableVariant>) {
+    ) -> (Self, PriorityQueueCalloopReceiver<GpuiRunnable>) {
         let (main_sender, main_receiver) = PriorityQueueCalloopReceiver::new();
 
         #[cfg(any(feature = "wayland", feature = "x11"))]

crates/gpui/src/platform/linux/wayland/client.rs 🔗

@@ -73,17 +73,14 @@ use super::{
     window::{ImeInput, WaylandWindowStatePtr},
 };
 
+use crate::platform::{PlatformWindow, blade::BladeContext};
 use crate::{
     AnyWindowHandle, Bounds, Capslock, CursorStyle, DOUBLE_CLICK_INTERVAL, DevicePixels, DisplayId,
     FileDropEvent, ForegroundExecutor, KeyDownEvent, KeyUpEvent, Keystroke, LinuxCommon,
     LinuxKeyboardLayout, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
     MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, PlatformDisplay,
     PlatformInput, PlatformKeyboardLayout, Point, ResultExt as _, SCROLL_LINES, ScrollDelta,
-    ScrollWheelEvent, Size, TouchPhase, WindowParams, point, profiler, px, size,
-};
-use crate::{
-    RunnableVariant, TaskTiming,
-    platform::{PlatformWindow, blade::BladeContext},
+    ScrollWheelEvent, Size, TouchPhase, WindowParams, point, px, size,
 };
 use crate::{
     SharedString,
@@ -498,38 +495,8 @@ impl WaylandClient {
                 let handle = handle.clone();
                 move |event, _, _: &mut WaylandClientStatePtr| {
                     if let calloop::channel::Event::Msg(runnable) = event {
-                        handle.insert_idle(|_| {
-                            let start = Instant::now();
-                            let mut timing = match runnable {
-                                RunnableVariant::Meta(runnable) => {
-                                    let location = runnable.metadata().location;
-                                    let timing = TaskTiming {
-                                        location,
-                                        start,
-                                        end: None,
-                                    };
-                                    profiler::add_task_timing(timing);
-
-                                    runnable.run();
-                                    timing
-                                }
-                                RunnableVariant::Compat(runnable) => {
-                                    let location = core::panic::Location::caller();
-                                    let timing = TaskTiming {
-                                        location,
-                                        start,
-                                        end: None,
-                                    };
-                                    profiler::add_task_timing(timing);
-
-                                    runnable.run();
-                                    timing
-                                }
-                            };
-
-                            let end = Instant::now();
-                            timing.end = Some(end);
-                            profiler::add_task_timing(timing);
+                        handle.insert_idle(move |_| {
+                            runnable.run_and_profile();
                         });
                     }
                 }

crates/gpui/src/platform/linux/x11/client.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{Capslock, ResultExt as _, RunnableVariant, TaskTiming, profiler, xcb_flush};
+use crate::{Capslock, ResultExt as _, xcb_flush};
 use anyhow::{Context as _, anyhow};
 use ashpd::WindowIdentifier;
 use calloop::{
@@ -313,37 +313,7 @@ impl X11Client {
                         // events have higher priority and runnables are only worked off after the event
                         // callbacks.
                         handle.insert_idle(|_| {
-                            let start = Instant::now();
-                            let mut timing = match runnable {
-                                RunnableVariant::Meta(runnable) => {
-                                    let location = runnable.metadata().location;
-                                    let timing = TaskTiming {
-                                        location,
-                                        start,
-                                        end: None,
-                                    };
-                                    profiler::add_task_timing(timing);
-
-                                    runnable.run();
-                                    timing
-                                }
-                                RunnableVariant::Compat(runnable) => {
-                                    let location = core::panic::Location::caller();
-                                    let timing = TaskTiming {
-                                        location,
-                                        start,
-                                        end: None,
-                                    };
-                                    profiler::add_task_timing(timing);
-
-                                    runnable.run();
-                                    timing
-                                }
-                            };
-
-                            let end = Instant::now();
-                            timing.end = Some(end);
-                            profiler::add_task_timing(timing);
+                            runnable.run_and_profile();
                         });
                     }
                 }

crates/gpui/src/platform/mac/dispatcher.rs 🔗

@@ -3,8 +3,8 @@
 #![allow(non_snake_case)]
 
 use crate::{
-    GLOBAL_THREAD_TIMINGS, PlatformDispatcher, Priority, RealtimePriority, RunnableMeta,
-    RunnableVariant, THREAD_TIMINGS, TaskLabel, TaskTiming, ThreadTaskTimings,
+    GLOBAL_THREAD_TIMINGS, GpuiRunnable, PlatformDispatcher, Priority, RealtimePriority,
+    RunnableMeta, THREAD_TIMINGS, TaskLabel, TaskTiming, ThreadTaskTimings,
 };
 
 use anyhow::Context;
@@ -28,7 +28,7 @@ use std::{
     ffi::c_void,
     mem::MaybeUninit,
     ptr::{NonNull, addr_of},
-    time::{Duration, Instant},
+    time::Duration,
 };
 use util::ResultExt;
 
@@ -69,25 +69,25 @@ impl PlatformDispatcher for MacDispatcher {
         is_main_thread == YES
     }
 
-    fn dispatch(&self, runnable: RunnableVariant, _: Option<TaskLabel>, priority: Priority) {
+    fn dispatch(&self, runnable: GpuiRunnable, _: Option<TaskLabel>) {
+        let queue_priority = match runnable.priority() {
+            Priority::Realtime(_) => unreachable!(),
+            Priority::High => DISPATCH_QUEUE_PRIORITY_HIGH as isize,
+            Priority::Medium => DISPATCH_QUEUE_PRIORITY_DEFAULT as isize,
+            Priority::Low => DISPATCH_QUEUE_PRIORITY_LOW as isize,
+        };
+
         let (context, trampoline) = match runnable {
-            RunnableVariant::Meta(runnable) => (
+            GpuiRunnable::GpuiSpawned(runnable) => (
                 runnable.into_raw().as_ptr() as *mut c_void,
                 Some(trampoline as unsafe extern "C" fn(*mut c_void)),
             ),
-            RunnableVariant::Compat(runnable) => (
+            GpuiRunnable::DependencySpawned(runnable) => (
                 runnable.into_raw().as_ptr() as *mut c_void,
                 Some(trampoline_compat as unsafe extern "C" fn(*mut c_void)),
             ),
         };
 
-        let queue_priority = match priority {
-            Priority::Realtime(_) => unreachable!(),
-            Priority::High => DISPATCH_QUEUE_PRIORITY_HIGH as isize,
-            Priority::Medium => DISPATCH_QUEUE_PRIORITY_DEFAULT as isize,
-            Priority::Low => DISPATCH_QUEUE_PRIORITY_LOW as isize,
-        };
-
         unsafe {
             dispatch_async_f(
                 dispatch_get_global_queue(queue_priority, 0),
@@ -97,13 +97,13 @@ impl PlatformDispatcher for MacDispatcher {
         }
     }
 
-    fn dispatch_on_main_thread(&self, runnable: RunnableVariant, _priority: Priority) {
+    fn dispatch_on_main_thread(&self, runnable: GpuiRunnable) {
         let (context, trampoline) = match runnable {
-            RunnableVariant::Meta(runnable) => (
+            GpuiRunnable::GpuiSpawned(runnable) => (
                 runnable.into_raw().as_ptr() as *mut c_void,
                 Some(trampoline as unsafe extern "C" fn(*mut c_void)),
             ),
-            RunnableVariant::Compat(runnable) => (
+            GpuiRunnable::DependencySpawned(runnable) => (
                 runnable.into_raw().as_ptr() as *mut c_void,
                 Some(trampoline_compat as unsafe extern "C" fn(*mut c_void)),
             ),
@@ -113,13 +113,13 @@ impl PlatformDispatcher for MacDispatcher {
         }
     }
 
-    fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant) {
+    fn dispatch_after(&self, duration: Duration, runnable: GpuiRunnable) {
         let (context, trampoline) = match runnable {
-            RunnableVariant::Meta(runnable) => (
+            GpuiRunnable::GpuiSpawned(runnable) => (
                 runnable.into_raw().as_ptr() as *mut c_void,
                 Some(trampoline as unsafe extern "C" fn(*mut c_void)),
             ),
-            RunnableVariant::Compat(runnable) => (
+            GpuiRunnable::DependencySpawned(runnable) => (
                 runnable.into_raw().as_ptr() as *mut c_void,
                 Some(trampoline_compat as unsafe extern "C" fn(*mut c_void)),
             ),
@@ -247,82 +247,19 @@ fn set_audio_thread_priority() -> anyhow::Result<()> {
     Ok(())
 }
 
+// Note we can not send through a GpuiRunnable as that would require allocating
+// to keep the pointer alive. So we recreate it here.
 extern "C" fn trampoline(runnable: *mut c_void) {
     let task =
         unsafe { Runnable::<RunnableMeta>::from_raw(NonNull::new_unchecked(runnable as *mut ())) };
-
-    let metadata = task.metadata();
-    let location = metadata.location;
-
-    if !metadata.is_app_alive() {
-        drop(task);
-        return;
-    }
-
-    let start = Instant::now();
-    let timing = TaskTiming {
-        location,
-        start,
-        end: None,
-    };
-
-    THREAD_TIMINGS.with(|timings| {
-        let mut timings = timings.lock();
-        let timings = &mut timings.timings;
-        if let Some(last_timing) = timings.iter_mut().rev().next() {
-            if last_timing.location == timing.location {
-                return;
-            }
-        }
-
-        timings.push_back(timing);
-    });
-
-    task.run();
-    let end = Instant::now();
-
-    THREAD_TIMINGS.with(|timings| {
-        let mut timings = timings.lock();
-        let timings = &mut timings.timings;
-        let Some(last_timing) = timings.iter_mut().rev().next() else {
-            return;
-        };
-        last_timing.end = Some(end);
-    });
+    let task = GpuiRunnable::GpuiSpawned(task);
+    task.run_and_profile();
 }
 
+// Note we can not send through a GpuiRunnable as that would require allocating
+// to keep the pointer alive. So we recreate it here.
 extern "C" fn trampoline_compat(runnable: *mut c_void) {
     let task = unsafe { Runnable::<()>::from_raw(NonNull::new_unchecked(runnable as *mut ())) };
-
-    let location = core::panic::Location::caller();
-
-    let start = Instant::now();
-    let timing = TaskTiming {
-        location,
-        start,
-        end: None,
-    };
-    THREAD_TIMINGS.with(|timings| {
-        let mut timings = timings.lock();
-        let timings = &mut timings.timings;
-        if let Some(last_timing) = timings.iter_mut().rev().next() {
-            if last_timing.location == timing.location {
-                return;
-            }
-        }
-
-        timings.push_back(timing);
-    });
-
-    task.run();
-    let end = Instant::now();
-
-    THREAD_TIMINGS.with(|timings| {
-        let mut timings = timings.lock();
-        let timings = &mut timings.timings;
-        let Some(last_timing) = timings.iter_mut().rev().next() else {
-            return;
-        };
-        last_timing.end = Some(end);
-    });
+    let task = GpuiRunnable::DependencySpawned(task);
+    task.run_and_profile();
 }

crates/gpui/src/platform/test/dispatcher.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{PlatformDispatcher, Priority, RunnableVariant, TaskLabel};
+use crate::{GpuiRunnable, PlatformDispatcher, TaskLabel};
 use backtrace::Backtrace;
 use collections::{HashMap, HashSet, VecDeque};
 use parking::Unparker;
@@ -25,10 +25,10 @@ pub struct TestDispatcher {
 
 struct TestDispatcherState {
     random: StdRng,
-    foreground: HashMap<TestDispatcherId, VecDeque<RunnableVariant>>,
-    background: Vec<RunnableVariant>,
-    deprioritized_background: Vec<RunnableVariant>,
-    delayed: Vec<(Duration, RunnableVariant)>,
+    foreground: HashMap<TestDispatcherId, VecDeque<GpuiRunnable>>,
+    background: Vec<GpuiRunnable>,
+    deprioritized_background: Vec<GpuiRunnable>,
+    delayed: Vec<(Duration, GpuiRunnable)>,
     start_time: Instant,
     time: Duration,
     is_main_thread: bool,
@@ -176,21 +176,10 @@ impl TestDispatcher {
         drop(state);
 
         // todo(localcc): add timings to tests
-        match runnable {
-            RunnableVariant::Meta(runnable) => {
-                if !runnable.metadata().is_app_alive() {
-                    drop(runnable);
-                } else {
-                    runnable.run();
-                }
-            }
-            RunnableVariant::Compat(runnable) => {
-                runnable.run();
-            }
-        };
-
+        if !runnable.app_dropped() {
+            runnable.run_unprofiled()
+        }
         self.state.lock().is_main_thread = was_main_thread;
-
         true
     }
 
@@ -292,7 +281,7 @@ impl PlatformDispatcher for TestDispatcher {
         state.start_time + state.time
     }
 
-    fn dispatch(&self, runnable: RunnableVariant, label: Option<TaskLabel>, _priority: Priority) {
+    fn dispatch(&self, runnable: GpuiRunnable, label: Option<TaskLabel>) {
         {
             let mut state = self.state.lock();
             if label.is_some_and(|label| state.deprioritized_task_labels.contains(&label)) {
@@ -304,7 +293,7 @@ impl PlatformDispatcher for TestDispatcher {
         self.unpark_all();
     }
 
-    fn dispatch_on_main_thread(&self, runnable: RunnableVariant, _priority: Priority) {
+    fn dispatch_on_main_thread(&self, runnable: GpuiRunnable) {
         self.state
             .lock()
             .foreground
@@ -314,7 +303,7 @@ impl PlatformDispatcher for TestDispatcher {
         self.unpark_all();
     }
 
-    fn dispatch_after(&self, duration: std::time::Duration, runnable: RunnableVariant) {
+    fn dispatch_after(&self, duration: std::time::Duration, runnable: GpuiRunnable) {
         let mut state = self.state.lock();
         let next_time = state.time + duration;
         let ix = match state.delayed.binary_search_by_key(&next_time, |e| e.0) {

crates/gpui/src/platform/windows/dispatcher.rs 🔗

@@ -1,7 +1,7 @@
 use std::{
     sync::atomic::{AtomicBool, Ordering},
     thread::{ThreadId, current},
-    time::{Duration, Instant},
+    time::Duration,
 };
 
 use anyhow::Context;
@@ -21,14 +21,14 @@ use windows::{
 };
 
 use crate::{
-    GLOBAL_THREAD_TIMINGS, HWND, PlatformDispatcher, Priority, PriorityQueueSender,
-    RealtimePriority, RunnableVariant, SafeHwnd, THREAD_TIMINGS, TaskLabel, TaskTiming,
-    ThreadTaskTimings, WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD, profiler,
+    GLOBAL_THREAD_TIMINGS, GpuiRunnable, HWND, PlatformDispatcher, Priority, PriorityQueueSender,
+    RealtimePriority, SafeHwnd, THREAD_TIMINGS, TaskLabel, ThreadTaskTimings,
+    WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD,
 };
 
 pub(crate) struct WindowsDispatcher {
     pub(crate) wake_posted: AtomicBool,
-    main_sender: PriorityQueueSender<RunnableVariant>,
+    main_sender: PriorityQueueSender<GpuiRunnable>,
     main_thread_id: ThreadId,
     pub(crate) platform_window_handle: SafeHwnd,
     validation_number: usize,
@@ -36,7 +36,7 @@ pub(crate) struct WindowsDispatcher {
 
 impl WindowsDispatcher {
     pub(crate) fn new(
-        main_sender: PriorityQueueSender<RunnableVariant>,
+        main_sender: PriorityQueueSender<GpuiRunnable>,
         platform_window_handle: HWND,
         validation_number: usize,
     ) -> Self {
@@ -52,11 +52,14 @@ impl WindowsDispatcher {
         }
     }
 
-    fn dispatch_on_threadpool(&self, priority: WorkItemPriority, runnable: RunnableVariant) {
+    fn dispatch_on_threadpool(&self, runnable: GpuiRunnable, priority: WorkItemPriority) {
         let handler = {
-            let mut task_wrapper = Some(runnable);
+            let mut runnable = Some(runnable);
             WorkItemHandler::new(move |_| {
-                Self::execute_runnable(task_wrapper.take().unwrap());
+                runnable
+                    .take()
+                    .expect("Takes FnMut but only runs once")
+                    .run_and_profile();
                 Ok(())
             })
         };
@@ -64,54 +67,19 @@ impl WindowsDispatcher {
         ThreadPool::RunWithPriorityAsync(&handler, priority).log_err();
     }
 
-    fn dispatch_on_threadpool_after(&self, runnable: RunnableVariant, duration: Duration) {
+    fn dispatch_on_threadpool_after(&self, runnable: GpuiRunnable, duration: Duration) {
         let handler = {
-            let mut task_wrapper = Some(runnable);
+            let mut runnable = Some(runnable);
             TimerElapsedHandler::new(move |_| {
-                Self::execute_runnable(task_wrapper.take().unwrap());
+                runnable
+                    .take()
+                    .expect("Takes FnMut but only runs once")
+                    .run_and_profile();
                 Ok(())
             })
         };
         ThreadPoolTimer::CreateTimer(&handler, duration.into()).log_err();
     }
-
-    #[inline(always)]
-    pub(crate) fn execute_runnable(runnable: RunnableVariant) {
-        let start = Instant::now();
-
-        let mut timing = match runnable {
-            RunnableVariant::Meta(runnable) => {
-                let location = runnable.metadata().location;
-                let timing = TaskTiming {
-                    location,
-                    start,
-                    end: None,
-                };
-                profiler::add_task_timing(timing);
-
-                runnable.run();
-
-                timing
-            }
-            RunnableVariant::Compat(runnable) => {
-                let timing = TaskTiming {
-                    location: core::panic::Location::caller(),
-                    start,
-                    end: None,
-                };
-                profiler::add_task_timing(timing);
-
-                runnable.run();
-
-                timing
-            }
-        };
-
-        let end = Instant::now();
-        timing.end = Some(end);
-
-        profiler::add_task_timing(timing);
-    }
 }
 
 impl PlatformDispatcher for WindowsDispatcher {
@@ -138,22 +106,21 @@ impl PlatformDispatcher for WindowsDispatcher {
         current().id() == self.main_thread_id
     }
 
-    fn dispatch(&self, runnable: RunnableVariant, label: Option<TaskLabel>, priority: Priority) {
-        let priority = match priority {
+    fn dispatch(&self, runnable: GpuiRunnable, label: Option<TaskLabel>) {
+        let priority = match runnable.priority() {
             Priority::Realtime(_) => unreachable!(),
             Priority::High => WorkItemPriority::High,
             Priority::Medium => WorkItemPriority::Normal,
             Priority::Low => WorkItemPriority::Low,
         };
-        self.dispatch_on_threadpool(priority, runnable);
-
+        self.dispatch_on_threadpool(runnable, priority);
         if let Some(label) = label {
             log::debug!("TaskLabel: {label:?}");
         }
     }
 
-    fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority) {
-        match self.main_sender.send(priority, runnable) {
+    fn dispatch_on_main_thread(&self, runnable: GpuiRunnable) {
+        match self.main_sender.send(runnable.priority(), runnable) {
             Ok(_) => {
                 if !self.wake_posted.swap(true, Ordering::AcqRel) {
                     unsafe {
@@ -181,7 +148,7 @@ impl PlatformDispatcher for WindowsDispatcher {
         }
     }
 
-    fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant) {
+    fn dispatch_after(&self, duration: Duration, runnable: GpuiRunnable) {
         self.dispatch_on_threadpool_after(runnable, duration);
     }
 

crates/gpui/src/platform/windows/events.rs 🔗

@@ -250,7 +250,7 @@ impl WindowsWindowInner {
         if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID {
             let mut runnables = self.main_receiver.clone().try_iter();
             while let Some(Ok(runnable)) = runnables.next() {
-                WindowsDispatcher::execute_runnable(runnable);
+                runnable.run_and_profile();
             }
             self.handle_paint_msg(handle)
         } else {

crates/gpui/src/platform/windows/platform.rs 🔗

@@ -51,7 +51,7 @@ struct WindowsPlatformInner {
     raw_window_handles: std::sync::Weak<RwLock<SmallVec<[SafeHwnd; 4]>>>,
     // The below members will never change throughout the entire lifecycle of the app.
     validation_number: usize,
-    main_receiver: PriorityQueueReceiver<RunnableVariant>,
+    main_receiver: PriorityQueueReceiver<GpuiRunnable>,
     dispatcher: Arc<WindowsDispatcher>,
 }
 
@@ -856,7 +856,7 @@ impl WindowsPlatformInner {
                 }
                 let mut main_receiver = self.main_receiver.clone();
                 match main_receiver.try_pop() {
-                    Ok(Some(runnable)) => WindowsDispatcher::execute_runnable(runnable),
+                    Ok(Some(runnable)) => _ = runnable.run_and_profile(),
                     _ => break 'timeout_loop,
                 }
             }
@@ -868,8 +868,7 @@ impl WindowsPlatformInner {
             match main_receiver.try_pop() {
                 Ok(Some(runnable)) => {
                     self.dispatcher.wake_posted.store(true, Ordering::Release);
-
-                    WindowsDispatcher::execute_runnable(runnable);
+                    runnable.run_and_profile();
                 }
                 _ => break 'tasks,
             }
@@ -933,7 +932,7 @@ pub(crate) struct WindowCreationInfo {
     pub(crate) windows_version: WindowsVersion,
     pub(crate) drop_target_helper: IDropTargetHelper,
     pub(crate) validation_number: usize,
-    pub(crate) main_receiver: PriorityQueueReceiver<RunnableVariant>,
+    pub(crate) main_receiver: PriorityQueueReceiver<GpuiRunnable>,
     pub(crate) platform_window_handle: HWND,
     pub(crate) disable_direct_composition: bool,
     pub(crate) directx_devices: DirectXDevices,
@@ -946,8 +945,8 @@ struct PlatformWindowCreateContext {
     inner: Option<Result<Rc<WindowsPlatformInner>>>,
     raw_window_handles: std::sync::Weak<RwLock<SmallVec<[SafeHwnd; 4]>>>,
     validation_number: usize,
-    main_sender: Option<PriorityQueueSender<RunnableVariant>>,
-    main_receiver: Option<PriorityQueueReceiver<RunnableVariant>>,
+    main_sender: Option<PriorityQueueSender<GpuiRunnable>>,
+    main_receiver: Option<PriorityQueueReceiver<GpuiRunnable>>,
     directx_devices: Option<DirectXDevices>,
     dispatcher: Option<Arc<WindowsDispatcher>>,
 }

crates/gpui/src/platform/windows/window.rs 🔗

@@ -82,7 +82,7 @@ pub(crate) struct WindowsWindowInner {
     pub(crate) executor: ForegroundExecutor,
     pub(crate) windows_version: WindowsVersion,
     pub(crate) validation_number: usize,
-    pub(crate) main_receiver: PriorityQueueReceiver<RunnableVariant>,
+    pub(crate) main_receiver: PriorityQueueReceiver<GpuiRunnable>,
     pub(crate) platform_window_handle: HWND,
     pub(crate) parent_hwnd: Option<HWND>,
 }
@@ -366,7 +366,7 @@ struct WindowCreateContext {
     windows_version: WindowsVersion,
     drop_target_helper: IDropTargetHelper,
     validation_number: usize,
-    main_receiver: PriorityQueueReceiver<RunnableVariant>,
+    main_receiver: PriorityQueueReceiver<GpuiRunnable>,
     platform_window_handle: HWND,
     appearance: WindowAppearance,
     disable_direct_composition: bool,

crates/repl/src/repl.rs 🔗

@@ -12,7 +12,7 @@ mod session;
 use std::{sync::Arc, time::Duration};
 
 use async_dispatcher::{Dispatcher, Runnable, set_dispatcher};
-use gpui::{App, PlatformDispatcher, Priority, RunnableVariant};
+use gpui::{App, GpuiRunnable, PlatformDispatcher};
 use project::Fs;
 pub use runtimelib::ExecutionState;
 
@@ -46,12 +46,12 @@ fn zed_dispatcher(cx: &mut App) -> impl Dispatcher {
     impl Dispatcher for ZedDispatcher {
         fn dispatch(&self, runnable: Runnable) {
             self.dispatcher
-                .dispatch(RunnableVariant::Compat(runnable), None, Priority::default());
+                .dispatch(GpuiRunnable::DependencySpawned(runnable), None);
         }
 
         fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
             self.dispatcher
-                .dispatch_after(duration, RunnableVariant::Compat(runnable));
+                .dispatch_after(duration, GpuiRunnable::DependencySpawned(runnable));
         }
     }