Compare commits

...

4 Commits

Author SHA1 Message Date
Lukas Wirth
f289ee7223 Drain tasks on exit 2025-12-17 15:23:03 +01:00
Lukas Wirth
f2d1fd091e quitting 2025-12-17 15:18:00 +01:00
Lukas Wirth
70855a0267 Better drop order in tests 2025-12-17 13:17:42 +01:00
Lukas Wirth
d0ec591b23 Detect leaked entities at the end of test runs 2025-12-17 12:28:46 +01:00
14 changed files with 279 additions and 80 deletions

View File

@@ -2235,7 +2235,9 @@ impl Editor {
diagnostics_max_severity,
hard_wrap: None,
completion_provider: project.clone().map(|project| Rc::new(project) as _),
semantics_provider: project.clone().map(|project| Rc::new(project) as _),
semantics_provider: project
.as_ref()
.map(|project| Rc::new(project.downgrade()) as _),
collaboration_hub: project.clone().map(|project| Box::new(project) as _),
project,
blink_manager: blink_manager.clone(),
@@ -21923,7 +21925,7 @@ impl Editor {
}
pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
let Some(project) = self.project.clone() else {
let Some(semantics) = self.semantics_provider.clone() else {
return;
};
@@ -21958,7 +21960,7 @@ impl Editor {
let range =
buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
project.inline_values(buffer, range, cx)
semantics.inline_values(buffer, range, cx)
})
.ok()
.flatten()?
@@ -24499,14 +24501,15 @@ impl CompletionProvider for Entity<Project> {
}
}
impl SemanticsProvider for Entity<Project> {
impl SemanticsProvider for WeakEntity<Project> {
fn hover(
&self,
buffer: &Entity<Buffer>,
position: text::Anchor,
cx: &mut App,
) -> Option<Task<Option<Vec<project::Hover>>>> {
Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
self.update(cx, |project, cx| project.hover(buffer, position, cx))
.ok()
}
fn document_highlights(
@@ -24515,9 +24518,10 @@ impl SemanticsProvider for Entity<Project> {
position: text::Anchor,
cx: &mut App,
) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
Some(self.update(cx, |project, cx| {
self.update(cx, |project, cx| {
project.document_highlights(buffer, position, cx)
}))
})
.ok()
}
fn definitions(
@@ -24527,12 +24531,13 @@ impl SemanticsProvider for Entity<Project> {
kind: GotoDefinitionKind,
cx: &mut App,
) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
Some(self.update(cx, |project, cx| match kind {
self.update(cx, |project, cx| match kind {
GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
}))
})
.ok()
}
fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
@@ -24548,6 +24553,7 @@ impl SemanticsProvider for Entity<Project> {
project.any_language_server_supports_inlay_hints(buffer, cx)
})
})
.unwrap_or(false)
}
fn inline_values(
@@ -24561,6 +24567,8 @@ impl SemanticsProvider for Entity<Project> {
Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
})
.ok()
.flatten()
}
fn applicable_inlay_chunks(
@@ -24569,15 +24577,21 @@ impl SemanticsProvider for Entity<Project> {
ranges: &[Range<text::Anchor>],
cx: &mut App,
) -> Vec<Range<BufferRow>> {
self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
self.update(cx, |project, cx| {
project.lsp_store().update(cx, |lsp_store, cx| {
lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
})
})
.unwrap_or_default()
}
fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
self.read(cx).lsp_store().update(cx, |lsp_store, _| {
lsp_store.invalidate_inlay_hints(for_buffers)
});
self.update(cx, |project, cx| {
project.lsp_store().update(cx, |lsp_store, _| {
lsp_store.invalidate_inlay_hints(for_buffers)
})
})
.ok();
}
fn inlay_hints(
@@ -24588,9 +24602,12 @@ impl SemanticsProvider for Entity<Project> {
known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
cx: &mut App,
) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
}))
self.update(cx, |project, cx| {
project.lsp_store().update(cx, |lsp_store, cx| {
lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
})
})
.ok()
}
fn range_for_rename(
@@ -24599,7 +24616,7 @@ impl SemanticsProvider for Entity<Project> {
position: text::Anchor,
cx: &mut App,
) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
Some(self.update(cx, |project, cx| {
self.update(cx, |project, cx| {
let buffer = buffer.clone();
let task = project.prepare_rename(buffer.clone(), position, cx);
cx.spawn(async move |_, cx| {
@@ -24622,7 +24639,8 @@ impl SemanticsProvider for Entity<Project> {
}
})
})
}))
})
.ok()
}
fn perform_rename(
@@ -24632,9 +24650,10 @@ impl SemanticsProvider for Entity<Project> {
new_name: String,
cx: &mut App,
) -> Option<Task<Result<ProjectTransaction>>> {
Some(self.update(cx, |project, cx| {
self.update(cx, |project, cx| {
project.perform_rename(buffer.clone(), position, new_name, cx)
}))
})
.ok()
}
}

View File

@@ -123,8 +123,6 @@ pub fn assert_text_with_selections(
assert_eq!(actual, marked_text, "Selections don't match");
}
// RA thinks this is dead code even though it is used in a whole lot of tests
#[allow(dead_code)]
#[cfg(any(test, feature = "test-support"))]
pub(crate) fn build_editor(
buffer: Entity<MultiBuffer>,

View File

@@ -743,6 +743,11 @@ impl App {
app
}
#[doc(hidden)]
pub fn ref_counts_drop_handle(&self) -> impl Sized + use<> {
self.entities.ref_counts_drop_handle()
}
/// Quit the application gracefully. Handlers registered with [`Context::on_app_quit`]
/// will be given 100ms to complete before exiting.
pub fn shutdown(&mut self) {

View File

@@ -3,7 +3,7 @@ use crate::{
Entity, EventEmitter, Focusable, ForegroundExecutor, Global, PromptButton, PromptLevel, Render,
Reservation, Result, Subscription, Task, VisualContext, Window, WindowHandle,
};
use anyhow::{Context as _, anyhow};
use anyhow::{Context as _, anyhow, bail};
use derive_more::{Deref, DerefMut};
use futures::channel::oneshot;
use std::{future::Future, rc::Weak};
@@ -29,12 +29,18 @@ impl AppContext for AsyncApp {
) -> Self::Result<Entity<T>> {
let app = self.app.upgrade().context("app was released")?;
let mut app = app.borrow_mut();
if app.quitting {
bail!("app is quitting");
}
Ok(app.new(build_entity))
}
fn reserve_entity<T: 'static>(&mut self) -> Result<Reservation<T>> {
let app = self.app.upgrade().context("app was released")?;
let mut app = app.borrow_mut();
if app.quitting {
bail!("app is quitting");
}
Ok(app.reserve_entity())
}
@@ -45,6 +51,9 @@ impl AppContext for AsyncApp {
) -> Result<Entity<T>> {
let app = self.app.upgrade().context("app was released")?;
let mut app = app.borrow_mut();
if app.quitting {
bail!("app is quitting");
}
Ok(app.insert_entity(reservation, build_entity))
}
@@ -55,6 +64,9 @@ impl AppContext for AsyncApp {
) -> Self::Result<R> {
let app = self.app.upgrade().context("app was released")?;
let mut app = app.borrow_mut();
if app.quitting {
bail!("app is quitting");
}
Ok(app.update_entity(handle, update))
}
@@ -77,6 +89,9 @@ impl AppContext for AsyncApp {
{
let app = self.app.upgrade().context("app was released")?;
let lock = app.borrow();
if lock.quitting {
bail!("app is quitting");
}
Ok(lock.read_entity(handle, callback))
}
@@ -86,6 +101,9 @@ impl AppContext for AsyncApp {
{
let app = self.app.upgrade().context("app was released")?;
let mut lock = app.try_borrow_mut()?;
if lock.quitting {
bail!("app is quitting");
}
lock.update_window(window, f)
}
@@ -99,6 +117,9 @@ impl AppContext for AsyncApp {
{
let app = self.app.upgrade().context("app was released")?;
let lock = app.borrow();
if lock.quitting {
bail!("app is quitting");
}
lock.read_window(window, read)
}
@@ -115,6 +136,9 @@ impl AppContext for AsyncApp {
{
let app = self.app.upgrade().context("app was released")?;
let mut lock = app.borrow_mut();
if lock.quitting {
bail!("app is quitting");
}
Ok(lock.update(|this| this.read_global(callback)))
}
}
@@ -124,6 +148,9 @@ impl AsyncApp {
pub fn refresh(&self) -> Result<()> {
let app = self.app.upgrade().context("app was released")?;
let mut lock = app.borrow_mut();
if lock.quitting {
bail!("app is quitting");
}
lock.refresh_windows();
Ok(())
}
@@ -142,6 +169,9 @@ impl AsyncApp {
pub fn update<R>(&self, f: impl FnOnce(&mut App) -> R) -> Result<R> {
let app = self.app.upgrade().context("app was released")?;
let mut lock = app.borrow_mut();
if lock.quitting {
bail!("app is quitting");
}
Ok(lock.update(f))
}
@@ -158,6 +188,9 @@ impl AsyncApp {
{
let app = self.app.upgrade().context("app was released")?;
let mut lock = app.borrow_mut();
if lock.quitting {
bail!("app is quitting");
}
let subscription = lock.subscribe(entity, on_event);
Ok(subscription)
}
@@ -173,6 +206,9 @@ impl AsyncApp {
{
let app = self.app.upgrade().context("app was released")?;
let mut lock = app.borrow_mut();
if lock.quitting {
bail!("app is quitting");
}
lock.open_window(options, build_root_view)
}
@@ -193,6 +229,9 @@ impl AsyncApp {
pub fn has_global<G: Global>(&self) -> Result<bool> {
let app = self.app.upgrade().context("app was released")?;
let app = app.borrow_mut();
if app.quitting {
bail!("app is quitting");
}
Ok(app.has_global::<G>())
}
@@ -203,6 +242,9 @@ impl AsyncApp {
pub fn read_global<G: Global, R>(&self, read: impl FnOnce(&G, &App) -> R) -> Result<R> {
let app = self.app.upgrade().context("app was released")?;
let app = app.borrow_mut();
if app.quitting {
bail!("app is quitting");
}
Ok(read(app.global(), &app))
}
@@ -215,6 +257,9 @@ impl AsyncApp {
pub fn try_read_global<G: Global, R>(&self, read: impl FnOnce(&G, &App) -> R) -> Option<R> {
let app = self.app.upgrade()?;
let app = app.borrow_mut();
if app.quitting {
return None;
}
Some(read(app.try_global()?, &app))
}
@@ -229,6 +274,9 @@ impl AsyncApp {
) -> Result<R> {
let app = self.app.upgrade().context("app was released")?;
let mut app = app.borrow_mut();
if app.quitting {
bail!("app is quitting");
}
app.update(|cx| {
cx.default_global::<G>();
});
@@ -243,6 +291,9 @@ impl AsyncApp {
) -> Result<R> {
let app = self.app.upgrade().context("app was released")?;
let mut app = app.borrow_mut();
if app.quitting {
bail!("app is quitting");
}
Ok(app.update(|cx| cx.update_global(update)))
}

View File

@@ -84,6 +84,11 @@ impl EntityMap {
}
}
#[doc(hidden)]
pub fn ref_counts_drop_handle(&self) -> impl Sized + use<> {
self.ref_counts.clone()
}
/// Reserve a slot for an entity, which you can subsequently use with `insert`.
pub fn reserve<T: 'static>(&self) -> Slot<T> {
let id = self.ref_counts.write().counts.insert(1.into());
@@ -227,7 +232,12 @@ pub struct AnyEntity {
}
impl AnyEntity {
fn new(id: EntityId, entity_type: TypeId, entity_map: Weak<RwLock<EntityRefCounts>>) -> Self {
fn new(
id: EntityId,
entity_type: TypeId,
entity_map: Weak<RwLock<EntityRefCounts>>,
#[cfg(any(test, feature = "leak-detection"))] type_name: &'static str,
) -> Self {
Self {
entity_id: id,
entity_type,
@@ -238,7 +248,7 @@ impl AnyEntity {
.unwrap()
.write()
.leak_detector
.handle_created(id),
.handle_created(id, Some(type_name)),
entity_map,
}
}
@@ -301,7 +311,7 @@ impl Clone for AnyEntity {
.unwrap()
.write()
.leak_detector
.handle_created(self.entity_id),
.handle_created(self.entity_id, None),
}
}
}
@@ -397,7 +407,13 @@ impl<T: 'static> Entity<T> {
T: 'static,
{
Self {
any_entity: AnyEntity::new(id, TypeId::of::<T>(), entity_map),
any_entity: AnyEntity::new(
id,
TypeId::of::<T>(),
entity_map,
#[cfg(any(test, feature = "leak-detection"))]
std::any::type_name::<T>(),
),
entity_type: PhantomData,
}
}
@@ -580,7 +596,7 @@ impl AnyWeakEntity {
.unwrap()
.write()
.leak_detector
.handle_created(self.entity_id),
.handle_created(self.entity_id, None),
})
}
@@ -907,7 +923,13 @@ pub(crate) struct HandleId {
#[cfg(any(test, feature = "leak-detection"))]
pub(crate) struct LeakDetector {
next_handle_id: u64,
entity_handles: HashMap<EntityId, HashMap<HandleId, Option<backtrace::Backtrace>>>,
entity_handles: HashMap<EntityId, EntityLeakData>,
}
#[cfg(any(test, feature = "leak-detection"))]
struct EntityLeakData {
handles: HashMap<HandleId, Option<backtrace::Backtrace>>,
type_name: &'static str,
}
#[cfg(any(test, feature = "leak-detection"))]
@@ -918,11 +940,21 @@ impl LeakDetector {
/// the handle is dropped. If `LEAK_BACKTRACE` is set, captures a backtrace
/// at the allocation site.
#[track_caller]
pub fn handle_created(&mut self, entity_id: EntityId) -> HandleId {
pub fn handle_created(
&mut self,
entity_id: EntityId,
type_name: Option<&'static str>,
) -> HandleId {
let id = util::post_inc(&mut self.next_handle_id);
let handle_id = HandleId { id };
let handles = self.entity_handles.entry(entity_id).or_default();
handles.insert(
let handles = self
.entity_handles
.entry(entity_id)
.or_insert_with(|| EntityLeakData {
handles: HashMap::default(),
type_name: type_name.unwrap_or("<unknown>"),
});
handles.handles.insert(
handle_id,
LEAK_BACKTRACE.then(backtrace::Backtrace::new_unresolved),
);
@@ -934,8 +966,14 @@ impl LeakDetector {
/// This removes the handle from tracking. The `handle_id` should be the same
/// one returned by `handle_created` when the handle was allocated.
pub fn handle_released(&mut self, entity_id: EntityId, handle_id: HandleId) {
let handles = self.entity_handles.entry(entity_id).or_default();
handles.remove(&handle_id);
if let std::collections::hash_map::Entry::Occupied(mut data) =
self.entity_handles.entry(entity_id)
{
data.get_mut().handles.remove(&handle_id);
if data.get().handles.is_empty() {
data.remove();
}
}
}
/// Asserts that all handles to the given entity have been released.
@@ -947,11 +985,10 @@ impl LeakDetector {
/// otherwise it suggests setting the environment variable to get more info.
pub fn assert_released(&mut self, entity_id: EntityId) {
use std::fmt::Write as _;
let handles = self.entity_handles.entry(entity_id).or_default();
if !handles.is_empty() {
if let Some(data) = self.entity_handles.remove(&entity_id) {
let mut out = String::new();
for backtrace in handles.values_mut() {
if let Some(mut backtrace) = backtrace.take() {
for (_, backtrace) in data.handles {
if let Some(mut backtrace) = backtrace {
backtrace.resolve();
writeln!(out, "Leaked handle:\n{:?}", backtrace).unwrap();
} else {
@@ -967,6 +1004,40 @@ impl LeakDetector {
}
}
#[cfg(any(test, feature = "leak-detection"))]
impl Drop for LeakDetector {
fn drop(&mut self) {
use std::fmt::Write;
if self.entity_handles.is_empty() {
return;
}
let mut out = String::new();
for (entity_id, data) in self.entity_handles.drain() {
for (_handle, backtrace) in data.handles {
if let Some(mut backtrace) = backtrace {
backtrace.resolve();
writeln!(
out,
"Leaked handle for entity {} ({entity_id:?}):\n{:?}",
data.type_name, backtrace
)
.unwrap();
} else {
writeln!(
out,
"Leaked handle for entity {} ({entity_id:?}): (export LEAK_BACKTRACE to find allocation site)",
data.type_name
)
.unwrap();
}
}
}
panic!("Exited with leaked handles:\n{out}");
}
}
#[cfg(test)]
mod test {
use crate::EntityMap;

View File

@@ -18,18 +18,17 @@ use std::{
/// an implementation of `Context` with additional methods that are useful in tests.
#[derive(Clone)]
pub struct TestAppContext {
#[doc(hidden)]
pub app: Rc<AppCell>,
#[doc(hidden)]
pub background_executor: BackgroundExecutor,
#[doc(hidden)]
pub foreground_executor: ForegroundExecutor,
#[doc(hidden)]
pub dispatcher: TestDispatcher,
dispatcher: TestDispatcher,
test_platform: Rc<TestPlatform>,
text_system: Arc<TextSystem>,
fn_name: Option<&'static str>,
on_quit: Rc<RefCell<Vec<Box<dyn FnOnce() + 'static>>>>,
#[doc(hidden)]
pub app: Rc<AppCell>,
}
impl AppContext for TestAppContext {
@@ -147,6 +146,11 @@ impl TestAppContext {
}
}
/// Drops all outstanding tasks from the dispatcher.
pub fn drain_tasks(&self) {
// self.dispatcher.drain_tasks();
}
/// Skip all drawing operations for the duration of this test.
pub fn skip_drawing(&mut self) {
self.app.borrow_mut().mode = GpuiMode::Test { skip_drawing: true };
@@ -411,8 +415,8 @@ impl TestAppContext {
}
/// Wait until there are no more pending tasks.
pub fn run_until_parked(&mut self) {
self.background_executor.run_until_parked()
pub fn run_until_parked(&self) {
self.dispatcher.run_until_parked();
}
/// Simulate dispatching an action to the currently focused node in the window.

View File

@@ -675,6 +675,11 @@ impl ForegroundExecutor {
}
}
#[doc(hidden)]
pub fn get_current_thread_timings(&self) -> Vec<TaskTiming> {
self.dispatcher.get_current_thread_timings()
}
/// Enqueues the given Task to run on the main thread at some point in the future.
#[track_caller]
pub fn spawn<R>(&self, future: impl Future<Output = R> + 'static) -> Task<R>
@@ -751,9 +756,11 @@ where
impl<F> Drop for Checked<F> {
fn drop(&mut self) {
assert!(
self.id == thread_id(),
"local task dropped by a thread that didn't spawn it. Task spawned at {}",
assert_eq!(
self.id,
thread_id(),
"local task dropped by thread {} that didn't spawn it. Task spawned at {}",
std::thread::current().name().unwrap_or("unknown"),
self.location
);
unsafe { ManuallyDrop::drop(&mut self.inner) };
@@ -764,9 +771,11 @@ where
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
assert!(
self.id == thread_id(),
"local task polled by a thread that didn't spawn it. Task spawned at {}",
assert_eq!(
self.id,
thread_id(),
"local task polled by thread {} that didn't spawn it. Task spawned at {}",
std::thread::current().name().unwrap_or("unknown"),
self.location
);
unsafe { self.map_unchecked_mut(|c| &mut *c.inner).poll(cx) }

View File

@@ -1,5 +1,5 @@
#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
#![warn(missing_docs)]
#![allow(clippy::type_complexity)] // Not useful, GPUI makes heavy use of callbacks
#![allow(clippy::collapsible_else_if)] // False positives in platform specific code
#![allow(unused_mut)] // False positives in platform specific code

View File

@@ -14,8 +14,8 @@ use std::{
};
use util::post_inc;
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
struct TestDispatcherId(usize);
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct TestDispatcherId(usize);
#[doc(hidden)]
pub struct TestDispatcher {
@@ -67,6 +67,32 @@ impl TestDispatcher {
}
}
pub fn drain_tasks(&self) {
// dropping runnables may reschedule tasks
// due to drop impls with executors in them
// so drop until we reach a fixpoint
loop {
let mut state = self.state.lock();
if state.background.is_empty()
&& state.deprioritized_background.is_empty()
&& state.delayed.is_empty()
&& state.foreground.is_empty()
{
break;
}
let background = std::mem::take(&mut state.background);
let deprioritized_background = std::mem::take(&mut state.deprioritized_background);
let delayed = std::mem::take(&mut state.delayed);
let foreground = std::mem::take(&mut state.foreground);
drop(state);
drop(background);
drop(deprioritized_background);
drop(delayed);
drop(foreground);
}
}
pub fn advance_clock(&self, by: Duration) {
let new_now = self.state.lock().time + by;
loop {

View File

@@ -165,12 +165,13 @@ fn generate_test_function(
dispatcher.clone(),
Some(stringify!(#outer_fn_name)),
);
let _entity_refcounts = #cx_varname.app.borrow().ref_counts_drop_handle();
));
cx_teardowns.extend(quote!(
dispatcher.run_until_parked();
#cx_varname.executor().forbid_parking();
#cx_varname.quit();
dispatcher.run_until_parked();
#cx_varname.run_until_parked();
#cx_varname.update(|cx| { cx.background_executor().forbid_parking(); cx.quit(); });
#cx_varname.run_until_parked();
drop(#cx_varname);
));
inner_fn_args.extend(quote!(&mut #cx_varname,));
continue;
@@ -191,10 +192,19 @@ fn generate_test_function(
&[#seeds],
#max_retries,
&mut |dispatcher, _seed| {
let executor = gpui::BackgroundExecutor::new(std::sync::Arc::new(dispatcher.clone()));
let exec = std::sync::Arc::new(dispatcher.clone());
#cx_vars
executor.block_test(#inner_fn_name(#inner_fn_args));
gpui::BackgroundExecutor::new(exec.clone()).block_test(#inner_fn_name(#inner_fn_args));
drop(exec);
#cx_teardowns
// Ideally we would only drop cancelled tasks, that way we could detect leaks due to task <-> entity
// cycles as cancelled tasks will be dropped properly once they runnable gets run again
// We can't do that in tests though, as well running tasks will run logic
// and we might just not exit that way
//
// async-task does not give us the power to do this just yet though
dispatcher.drain_tasks();
drop(dispatcher);
},
#on_failure_fn_name
);
@@ -229,13 +239,15 @@ fn generate_test_function(
Some(stringify!(#outer_fn_name))
);
let mut #cx_varname_lock = #cx_varname.app.borrow_mut();
let _entity_refcounts = #cx_varname_lock.ref_counts_drop_handle();
));
inner_fn_args.extend(quote!(&mut #cx_varname_lock,));
cx_teardowns.extend(quote!(
drop(#cx_varname_lock);
dispatcher.run_until_parked();
#cx_varname.run_until_parked();
#cx_varname.update(|cx| { cx.background_executor().forbid_parking(); cx.quit(); });
dispatcher.run_until_parked();
#cx_varname.run_until_parked();
drop(#cx_varname);
));
continue;
}
@@ -246,12 +258,13 @@ fn generate_test_function(
dispatcher.clone(),
Some(stringify!(#outer_fn_name))
);
let _entity_refcounts = #cx_varname.app.borrow().ref_counts_drop_handle();
));
cx_teardowns.extend(quote!(
dispatcher.run_until_parked();
#cx_varname.executor().forbid_parking();
#cx_varname.quit();
dispatcher.run_until_parked();
#cx_varname.run_until_parked();
#cx_varname.update(|cx| { cx.background_executor().forbid_parking(); cx.quit(); });
#cx_varname.run_until_parked();
drop(#cx_varname);
));
inner_fn_args.extend(quote!(&mut #cx_varname,));
continue;
@@ -277,6 +290,14 @@ fn generate_test_function(
#cx_vars
#inner_fn_name(#inner_fn_args);
#cx_teardowns
// Ideally we would only drop cancelled tasks, that way we could detect leaks due to task <-> entity
// cycles as cancelled tasks will be dropped properly once they runnable gets run again
// We can't do that in tests though, as well running tasks will run logic
// and we might just not exit that way
//
// async-task does not give us the power to do this just yet though
dispatcher.drain_tasks();
drop(dispatcher);
},
#on_failure_fn_name,
);

View File

@@ -123,10 +123,7 @@ impl ProfilerWindow {
fn begin_listen(cx: &mut Context<Self>) -> Task<()> {
cx.spawn(async move |this, cx| {
loop {
let data = cx
.foreground_executor()
.dispatcher
.get_current_thread_timings();
let data = cx.foreground_executor().get_current_thread_timings();
this.update(cx, |this: &mut ProfilerWindow, cx| {
this.data = DataMode::Realtime(Some(data));

View File

@@ -1,6 +1,5 @@
use std::{
any::Any,
borrow::Borrow,
path::{Path, PathBuf},
str::FromStr as _,
sync::Arc,
@@ -84,7 +83,7 @@ impl From<ExternalAgentServerName> for SharedString {
}
}
impl Borrow<str> for ExternalAgentServerName {
impl std::borrow::Borrow<str> for ExternalAgentServerName {
fn borrow(&self) -> &str {
&self.0
}

View File

@@ -1150,10 +1150,9 @@ impl SettingsObserver {
let mut user_tasks_file_rx =
watch_config_file(cx.background_executor(), fs, file_path.clone());
let user_tasks_content = cx.background_executor().block(user_tasks_file_rx.next());
let weak_entry = cx.weak_entity();
cx.spawn(async move |settings_observer, cx| {
let Ok(task_store) = settings_observer.read_with(cx, |settings_observer, _| {
settings_observer.task_store.clone()
settings_observer.task_store.downgrade()
}) else {
return;
};
@@ -1181,7 +1180,7 @@ impl SettingsObserver {
break;
};
weak_entry
settings_observer
.update(cx, |_, cx| match result {
Ok(()) => cx.emit(SettingsObserverEvent::LocalTasksUpdated(Ok(
file_path.clone()
@@ -1205,10 +1204,9 @@ impl SettingsObserver {
let mut user_tasks_file_rx =
watch_config_file(cx.background_executor(), fs, file_path.clone());
let user_tasks_content = cx.background_executor().block(user_tasks_file_rx.next());
let weak_entry = cx.weak_entity();
cx.spawn(async move |settings_observer, cx| {
let Ok(task_store) = settings_observer.read_with(cx, |settings_observer, _| {
settings_observer.task_store.clone()
settings_observer.task_store.downgrade()
}) else {
return;
};
@@ -1236,7 +1234,7 @@ impl SettingsObserver {
break;
};
weak_entry
settings_observer
.update(cx, |_, cx| match result {
Ok(()) => cx.emit(SettingsObserverEvent::LocalDebugScenariosUpdated(Ok(
file_path.clone(),

View File

@@ -188,8 +188,9 @@ where
impl<F> Drop for Checked<F> {
fn drop(&mut self) {
assert!(
self.id == thread_id(),
assert_eq!(
self.id,
thread_id(),
"local task dropped by a thread that didn't spawn it. Task spawned at {}",
self.location
);