Detailed changes
@@ -138,8 +138,10 @@ impl Application {
#[cfg(any(test, feature = "test-support"))]
log::info!("GPUI was compiled in test mode");
+ let liveness = Arc::new(());
Self(App::new_app(
- current_platform(false),
+ current_platform(false, Arc::downgrade(&liveness)),
+ liveness,
Arc::new(()),
Arc::new(NullHttpClient),
))
@@ -149,8 +151,10 @@ impl Application {
/// but makes it possible to run an application in an context like
/// SSH, where GUI applications are not allowed.
pub fn headless() -> Self {
+ let liveness = Arc::new(());
Self(App::new_app(
- current_platform(true),
+ current_platform(true, Arc::downgrade(&liveness)),
+ liveness,
Arc::new(()),
Arc::new(NullHttpClient),
))
@@ -584,7 +588,7 @@ impl GpuiMode {
/// You need a reference to an `App` to access the state of a [Entity].
pub struct App {
pub(crate) this: Weak<AppCell>,
- pub(crate) liveness: std::sync::Arc<()>,
+ pub(crate) _liveness: Arc<()>,
pub(crate) platform: Rc<dyn Platform>,
pub(crate) mode: GpuiMode,
text_system: Arc<TextSystem>,
@@ -646,13 +650,14 @@ impl App {
#[allow(clippy::new_ret_no_self)]
pub(crate) fn new_app(
platform: Rc<dyn Platform>,
+ liveness: Arc<()>,
asset_source: Arc<dyn AssetSource>,
http_client: Arc<dyn HttpClient>,
) -> Rc<AppCell> {
- let executor = platform.background_executor();
+ let background_executor = platform.background_executor();
let foreground_executor = platform.foreground_executor();
assert!(
- executor.is_main_thread(),
+ background_executor.is_main_thread(),
"must construct App on main thread"
);
@@ -664,7 +669,7 @@ impl App {
let app = Rc::new_cyclic(|this| AppCell {
app: RefCell::new(App {
this: this.clone(),
- liveness: std::sync::Arc::new(()),
+ _liveness: liveness,
platform: platform.clone(),
text_system,
text_rendering_mode: Rc::new(Cell::new(TextRenderingMode::default())),
@@ -673,7 +678,7 @@ impl App {
flushing_effects: false,
pending_updates: 0,
active_drag: None,
- background_executor: executor,
+ background_executor,
foreground_executor,
svg_renderer: SvgRenderer::new(asset_source.clone()),
loading_assets: Default::default(),
@@ -1494,7 +1499,6 @@ impl App {
pub fn to_async(&self) -> AsyncApp {
AsyncApp {
app: self.this.clone(),
- liveness_token: std::sync::Arc::downgrade(&self.liveness),
background_executor: self.background_executor.clone(),
foreground_executor: self.foreground_executor.clone(),
}
@@ -16,7 +16,6 @@ use super::{Context, WeakEntity};
#[derive(Clone)]
pub struct AsyncApp {
pub(crate) app: Weak<AppCell>,
- pub(crate) liveness_token: std::sync::Weak<()>,
pub(crate) background_executor: BackgroundExecutor,
pub(crate) foreground_executor: ForegroundExecutor,
}
@@ -186,7 +185,7 @@ impl AsyncApp {
{
let mut cx = self.clone();
self.foreground_executor
- .spawn_context(self.liveness_token.clone(), async move { f(&mut cx).await })
+ .spawn(async move { f(&mut cx).await })
}
/// Determine whether global state of the specified type has been assigned.
@@ -335,10 +334,7 @@ impl AsyncWindowContext {
{
let mut cx = self.clone();
self.foreground_executor
- .spawn_context(
- self.app.liveness_token.clone(),
- async move { f(&mut cx).await },
- )
+ .spawn(async move { f(&mut cx).await })
}
/// Present a platform dialog.
@@ -125,14 +125,16 @@ impl TestAppContext {
/// Creates a new `TestAppContext`. Usually you can rely on `#[gpui::test]` to do this for you.
pub fn build(dispatcher: TestDispatcher, fn_name: Option<&'static str>) -> Self {
let arc_dispatcher = Arc::new(dispatcher.clone());
+ let liveness = std::sync::Arc::new(());
let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
- let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
+ let foreground_executor =
+ ForegroundExecutor::new(arc_dispatcher, Arc::downgrade(&liveness));
let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
let asset_source = Arc::new(());
let http_client = http_client::FakeHttpClient::with_404_response();
let text_system = Arc::new(TextSystem::new(platform.text_system()));
- let mut app = App::new_app(platform.clone(), asset_source, http_client);
+ let app = App::new_app(platform.clone(), liveness, asset_source, http_client);
app.borrow_mut().mode = GpuiMode::test();
Self {
@@ -405,7 +407,6 @@ impl TestAppContext {
pub fn to_async(&self) -> AsyncApp {
AsyncApp {
app: Rc::downgrade(&self.app),
- liveness_token: std::sync::Arc::downgrade(&self.app.borrow().liveness),
background_executor: self.background_executor.clone(),
foreground_executor: self.foreground_executor.clone(),
}
@@ -37,7 +37,9 @@ impl VisualTestAppContext {
/// - Screenshots can be captured via ScreenCaptureKit
/// - All platform APIs work as they do in production
pub fn new() -> Self {
- let platform = current_platform(false);
+ let liveness = Arc::new(());
+ let liveness_weak = Arc::downgrade(&liveness);
+ let platform = current_platform(false, liveness_weak);
let background_executor = platform.background_executor();
let foreground_executor = platform.foreground_executor();
let text_system = Arc::new(TextSystem::new(platform.text_system()));
@@ -45,7 +47,7 @@ impl VisualTestAppContext {
let asset_source = Arc::new(());
let http_client = http_client::FakeHttpClient::with_404_response();
- let mut app = App::new_app(platform.clone(), asset_source, http_client);
+ let mut app = App::new_app(platform.clone(), liveness, asset_source, http_client);
app.borrow_mut().mode = GpuiMode::test();
Self {
@@ -19,7 +19,7 @@ use std::{
thread::{self, ThreadId},
time::{Duration, Instant},
};
-use util::TryFutureExt;
+use util::TryFutureExt as _;
use waker_fn::waker_fn;
#[cfg(any(test, feature = "test-support"))]
@@ -44,6 +44,7 @@ pub struct BackgroundExecutor {
pub struct ForegroundExecutor {
#[doc(hidden)]
pub dispatcher: Arc<dyn PlatformDispatcher>,
+ liveness: std::sync::Weak<()>,
not_send: PhantomData<Rc<()>>,
}
@@ -776,9 +777,10 @@ impl BackgroundExecutor {
/// ForegroundExecutor runs things on the main thread.
impl ForegroundExecutor {
/// Creates a new ForegroundExecutor from the given PlatformDispatcher.
- pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
+ pub fn new(dispatcher: Arc<dyn PlatformDispatcher>, liveness: std::sync::Weak<()>) -> Self {
Self {
dispatcher,
+ liveness,
not_send: PhantomData,
}
}
@@ -789,7 +791,7 @@ impl ForegroundExecutor {
where
R: 'static,
{
- self.inner_spawn(None, Priority::default(), future)
+ self.inner_spawn(self.liveness.clone(), Priority::default(), future)
}
/// Enqueues the given Task to run on the main thread at some point in the future.
@@ -802,25 +804,13 @@ impl ForegroundExecutor {
where
R: 'static,
{
- self.inner_spawn(None, priority, future)
- }
-
- #[track_caller]
- pub(crate) fn spawn_context<R>(
- &self,
- app: std::sync::Weak<()>,
- future: impl Future<Output = R> + 'static,
- ) -> Task<R>
- where
- R: 'static,
- {
- self.inner_spawn(Some(app), Priority::default(), future)
+ self.inner_spawn(self.liveness.clone(), priority, future)
}
#[track_caller]
pub(crate) fn inner_spawn<R>(
&self,
- app: Option<std::sync::Weak<()>>,
+ app: std::sync::Weak<()>,
priority: Priority,
future: impl Future<Output = R> + 'static,
) -> Task<R>
@@ -835,7 +825,7 @@ impl ForegroundExecutor {
dispatcher: Arc<dyn PlatformDispatcher>,
future: AnyLocalFuture<R>,
location: &'static core::panic::Location<'static>,
- app: Option<std::sync::Weak<()>>,
+ app: std::sync::Weak<()>,
priority: Priority,
) -> Task<R> {
let (runnable, task) = spawn_local_with_source_location(
@@ -843,7 +833,10 @@ impl ForegroundExecutor {
move |runnable| {
dispatcher.dispatch_on_main_thread(RunnableVariant::Meta(runnable), priority)
},
- RunnableMeta { location, app },
+ RunnableMeta {
+ location,
+ app: Some(app),
+ },
);
runnable.schedule();
Task(TaskState::Spawned(task))
@@ -989,24 +982,34 @@ mod test {
use rand::SeedableRng;
use std::cell::RefCell;
- #[test]
- fn sanity_test_tasks_run() {
+ /// Helper to create test infrastructure.
+ /// Returns (dispatcher, background_executor, app) where app's foreground_executor has liveness.
+ fn create_test_app() -> (TestDispatcher, BackgroundExecutor, Rc<crate::AppCell>) {
let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0));
let arc_dispatcher = Arc::new(dispatcher.clone());
+ // Create liveness for task cancellation
+ let liveness = std::sync::Arc::new(());
+ let liveness_weak = std::sync::Arc::downgrade(&liveness);
let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
- let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
+ let foreground_executor = ForegroundExecutor::new(arc_dispatcher, liveness_weak);
- let platform = TestPlatform::new(background_executor, foreground_executor.clone());
+ let platform = TestPlatform::new(background_executor.clone(), foreground_executor);
let asset_source = Arc::new(());
let http_client = http_client::FakeHttpClient::with_404_response();
- let app = App::new_app(platform, asset_source, http_client);
- let liveness_token = std::sync::Arc::downgrade(&app.borrow().liveness);
+ let app = App::new_app(platform, liveness, asset_source, http_client);
+ (dispatcher, background_executor, app)
+ }
+
+ #[test]
+ fn sanity_test_tasks_run() {
+ let (dispatcher, _background_executor, app) = create_test_app();
+ let foreground_executor = app.borrow().foreground_executor.clone();
let task_ran = Rc::new(RefCell::new(false));
foreground_executor
- .spawn_context(liveness_token, {
+ .spawn({
let task_ran = Rc::clone(&task_ran);
async move {
*task_ran.borrow_mut() = true;
@@ -1026,24 +1029,15 @@ mod test {
#[test]
fn test_task_cancelled_when_app_dropped() {
- let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0));
- let arc_dispatcher = Arc::new(dispatcher.clone());
- let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
- let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
-
- let platform = TestPlatform::new(background_executor, foreground_executor.clone());
- let asset_source = Arc::new(());
- let http_client = http_client::FakeHttpClient::with_404_response();
-
- let app = App::new_app(platform, asset_source, http_client);
- let liveness_token = std::sync::Arc::downgrade(&app.borrow().liveness);
+ let (dispatcher, _background_executor, app) = create_test_app();
+ let foreground_executor = app.borrow().foreground_executor.clone();
let app_weak = Rc::downgrade(&app);
let task_ran = Rc::new(RefCell::new(false));
let task_ran_clone = Rc::clone(&task_ran);
foreground_executor
- .spawn_context(liveness_token, async move {
+ .spawn(async move {
*task_ran_clone.borrow_mut() = true;
})
.detach();
@@ -1063,17 +1057,8 @@ mod test {
#[test]
fn test_nested_tasks_both_cancel() {
- let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0));
- let arc_dispatcher = Arc::new(dispatcher.clone());
- let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
- let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
-
- let platform = TestPlatform::new(background_executor, foreground_executor.clone());
- let asset_source = Arc::new(());
- let http_client = http_client::FakeHttpClient::with_404_response();
-
- let app = App::new_app(platform, asset_source, http_client);
- let liveness_token = std::sync::Arc::downgrade(&app.borrow().liveness);
+ let (dispatcher, _background_executor, app) = create_test_app();
+ let foreground_executor = app.borrow().foreground_executor.clone();
let app_weak = Rc::downgrade(&app);
let outer_completed = Rc::new(RefCell::new(false));
@@ -1087,13 +1072,11 @@ mod test {
// Channel to block the inner task until we're ready
let (tx, rx) = futures::channel::oneshot::channel::<()>();
- // We need clones of executor and liveness_token for the inner spawn
let inner_executor = foreground_executor.clone();
- let inner_liveness_token = liveness_token.clone();
foreground_executor
- .spawn_context(liveness_token, async move {
- let inner_task = inner_executor.spawn_context(inner_liveness_token, {
+ .spawn(async move {
+ let inner_task = inner_executor.spawn({
let inner_flag = Rc::clone(&inner_flag);
async move {
rx.await.ok();
@@ -1148,56 +1131,14 @@ mod test {
);
}
- #[test]
- fn test_task_without_app_tracking_still_runs() {
- let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0));
- let arc_dispatcher = Arc::new(dispatcher.clone());
- let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
- let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
-
- let platform = TestPlatform::new(background_executor, foreground_executor.clone());
- let asset_source = Arc::new(());
- let http_client = http_client::FakeHttpClient::with_404_response();
-
- let app = App::new_app(platform, asset_source, http_client);
- let app_weak = Rc::downgrade(&app);
-
- let task_ran = Rc::new(RefCell::new(false));
- let task_ran_clone = Rc::clone(&task_ran);
-
- let _task = foreground_executor.spawn(async move {
- *task_ran_clone.borrow_mut() = true;
- });
-
- drop(app);
-
- assert!(app_weak.upgrade().is_none(), "App should have been dropped");
-
- dispatcher.run_until_parked();
-
- assert!(
- *task_ran.borrow(),
- "Task without app tracking should still run after app is dropped"
- );
- }
-
#[test]
#[should_panic]
fn test_polling_cancelled_task_panics() {
- let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0));
- let arc_dispatcher = Arc::new(dispatcher.clone());
- let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
- let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
-
- let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
- let asset_source = Arc::new(());
- let http_client = http_client::FakeHttpClient::with_404_response();
-
- let app = App::new_app(platform, asset_source, http_client);
- let liveness_token = std::sync::Arc::downgrade(&app.borrow().liveness);
+ let (dispatcher, background_executor, app) = create_test_app();
+ let foreground_executor = app.borrow().foreground_executor.clone();
let app_weak = Rc::downgrade(&app);
- let task = foreground_executor.spawn_context(liveness_token, async move { 42 });
+ let task = foreground_executor.spawn(async move { 42 });
drop(app);
@@ -1210,22 +1151,11 @@ mod test {
#[test]
fn test_polling_cancelled_task_returns_none_with_fallible() {
- let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0));
- let arc_dispatcher = Arc::new(dispatcher.clone());
- let background_executor = BackgroundExecutor::new(arc_dispatcher.clone());
- let foreground_executor = ForegroundExecutor::new(arc_dispatcher);
-
- let platform = TestPlatform::new(background_executor.clone(), foreground_executor.clone());
- let asset_source = Arc::new(());
- let http_client = http_client::FakeHttpClient::with_404_response();
-
- let app = App::new_app(platform, asset_source, http_client);
- let liveness_token = std::sync::Arc::downgrade(&app.borrow().liveness);
+ let (dispatcher, background_executor, app) = create_test_app();
+ let foreground_executor = app.borrow().foreground_executor.clone();
let app_weak = Rc::downgrade(&app);
- let task = foreground_executor
- .spawn_context(liveness_token, async move { 42 })
- .fallible();
+ let task = foreground_executor.spawn(async move { 42 }).fallible();
drop(app);
@@ -92,43 +92,45 @@ pub use test::{TestDispatcher, TestScreenCaptureSource, TestScreenCaptureStream}
/// Returns a background executor for the current platform.
pub fn background_executor() -> BackgroundExecutor {
- current_platform(true).background_executor()
+ // For standalone background executor, use a dead liveness since there's no App.
+ // Weak::new() creates a weak reference that always returns None on upgrade.
+ current_platform(true, std::sync::Weak::new()).background_executor()
}
#[cfg(target_os = "macos")]
-pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
- Rc::new(MacPlatform::new(headless))
+pub(crate) fn current_platform(headless: bool, liveness: std::sync::Weak<()>) -> Rc<dyn Platform> {
+ Rc::new(MacPlatform::new(headless, liveness))
}
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
-pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
+pub(crate) fn current_platform(headless: bool, liveness: std::sync::Weak<()>) -> Rc<dyn Platform> {
#[cfg(feature = "x11")]
use anyhow::Context as _;
if headless {
- return Rc::new(HeadlessClient::new());
+ return Rc::new(HeadlessClient::new(liveness));
}
match guess_compositor() {
#[cfg(feature = "wayland")]
- "Wayland" => Rc::new(WaylandClient::new()),
+ "Wayland" => Rc::new(WaylandClient::new(liveness)),
#[cfg(feature = "x11")]
"X11" => Rc::new(
- X11Client::new()
+ X11Client::new(liveness)
.context("Failed to initialize X11 client.")
.unwrap(),
),
- "Headless" => Rc::new(HeadlessClient::new()),
+ "Headless" => Rc::new(HeadlessClient::new(liveness)),
_ => unreachable!(),
}
}
#[cfg(target_os = "windows")]
-pub(crate) fn current_platform(_headless: bool) -> Rc<dyn Platform> {
+pub(crate) fn current_platform(_headless: bool, liveness: std::sync::Weak<()>) -> Rc<dyn Platform> {
Rc::new(
- WindowsPlatform::new()
+ WindowsPlatform::new(liveness)
.inspect_err(|err| show_error("Failed to launch", err.to_string()))
.unwrap(),
)
@@ -21,10 +21,10 @@ pub struct HeadlessClientState {
pub(crate) struct HeadlessClient(Rc<RefCell<HeadlessClientState>>);
impl HeadlessClient {
- pub(crate) fn new() -> Self {
+ pub(crate) fn new(liveness: std::sync::Weak<()>) -> Self {
let event_loop = EventLoop::try_new().unwrap();
- let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
+ let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal(), liveness);
let handle = event_loop.handle();
@@ -149,7 +149,10 @@ pub(crate) struct LinuxCommon {
}
impl LinuxCommon {
- pub fn new(signal: LoopSignal) -> (Self, PriorityQueueCalloopReceiver<RunnableVariant>) {
+ pub fn new(
+ signal: LoopSignal,
+ liveness: std::sync::Weak<()>,
+ ) -> (Self, PriorityQueueCalloopReceiver<RunnableVariant>) {
let (main_sender, main_receiver) = PriorityQueueCalloopReceiver::new();
#[cfg(any(feature = "wayland", feature = "x11"))]
@@ -165,7 +168,7 @@ impl LinuxCommon {
let common = LinuxCommon {
background_executor,
- foreground_executor: ForegroundExecutor::new(dispatcher),
+ foreground_executor: ForegroundExecutor::new(dispatcher, liveness),
text_system,
appearance: WindowAppearance::Light,
auto_hide_scrollbars: false,
@@ -453,7 +453,7 @@ fn wl_output_version(version: u32) -> u32 {
}
impl WaylandClient {
- pub(crate) fn new() -> Self {
+ pub(crate) fn new(liveness: std::sync::Weak<()>) -> Self {
let conn = Connection::connect_to_env().unwrap();
let (globals, mut event_queue) =
@@ -490,7 +490,7 @@ impl WaylandClient {
let event_loop = EventLoop::<WaylandClientStatePtr>::try_new().unwrap();
- let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
+ let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal(), liveness);
let handle = event_loop.handle();
handle
@@ -297,10 +297,10 @@ impl X11ClientStatePtr {
pub(crate) struct X11Client(Rc<RefCell<X11ClientState>>);
impl X11Client {
- pub(crate) fn new() -> anyhow::Result<Self> {
+ pub(crate) fn new(liveness: std::sync::Weak<()>) -> anyhow::Result<Self> {
let event_loop = EventLoop::try_new()?;
- let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal());
+ let (common, main_receiver) = LinuxCommon::new(event_loop.get_signal(), liveness);
let handle = event_loop.handle();
@@ -173,14 +173,8 @@ pub(crate) struct MacPlatformState {
keyboard_mapper: Rc<MacKeyboardMapper>,
}
-impl Default for MacPlatform {
- fn default() -> Self {
- Self::new(false)
- }
-}
-
impl MacPlatform {
- pub(crate) fn new(headless: bool) -> Self {
+ pub(crate) fn new(headless: bool, liveness: std::sync::Weak<()>) -> Self {
let dispatcher = Arc::new(MacDispatcher);
#[cfg(feature = "font-kit")]
@@ -196,7 +190,7 @@ impl MacPlatform {
headless,
text_system,
background_executor: BackgroundExecutor::new(dispatcher.clone()),
- foreground_executor: ForegroundExecutor::new(dispatcher),
+ foreground_executor: ForegroundExecutor::new(dispatcher, liveness),
renderer_context: renderer::Context::default(),
general_pasteboard: Pasteboard::general(),
find_pasteboard: Pasteboard::find(),
@@ -180,12 +180,13 @@ impl TestDispatcher {
RunnableVariant::Meta(runnable) => {
if !runnable.metadata().is_app_alive() {
drop(runnable);
- self.state.lock().is_main_thread = was_main_thread;
- return true;
+ } else {
+ runnable.run();
}
- runnable.run()
}
- RunnableVariant::Compat(runnable) => runnable.run(),
+ RunnableVariant::Compat(runnable) => {
+ runnable.run();
+ }
};
self.state.lock().is_main_thread = was_main_thread;
@@ -93,7 +93,7 @@ impl WindowsPlatformState {
}
impl WindowsPlatform {
- pub(crate) fn new() -> Result<Self> {
+ pub(crate) fn new(liveness: std::sync::Weak<()>) -> Result<Self> {
unsafe {
OleInitialize(None).context("unable to initialize Windows OLE")?;
}
@@ -148,7 +148,7 @@ impl WindowsPlatform {
let disable_direct_composition = std::env::var(DISABLE_DIRECT_COMPOSITION)
.is_ok_and(|value| value == "true" || value == "1");
let background_executor = BackgroundExecutor::new(dispatcher.clone());
- let foreground_executor = ForegroundExecutor::new(dispatcher);
+ let foreground_executor = ForegroundExecutor::new(dispatcher, liveness);
let drop_target_helper: IDropTargetHelper = unsafe {
CoCreateInstance(&CLSID_DragDropHelper, None, CLSCTX_INPROC_SERVER)