zed-industries/zed · error
validated in BenchAppContext::build
Error message
validated in BenchAppContext::build
What it means
`BenchAppContext::run_until_idle` calls `background_executor.dispatcher().as_threaded().expect("validated in BenchAppContext::build")`. This panics because the dispatcher behind the context's executors is not a `ThreadedDispatcher`. The library normally guarantees this in `BenchAppContext::build` (an assert on line 533 requires a platform constructed via `gpui::bench_platform`), so hitting the expect means the context was built against a non-threaded platform — typically the deterministic test platform used by `TestAppContext` — bypassing that validation.
Source
Thrown at crates/gpui/src/app/bench_context.rs:592
}
/// Reads app state.
pub fn read<R>(&self, read: impl FnOnce(&App) -> R) -> R {
let app = self.app.borrow();
read(&app)
}
/// Runs queued foreground tasks on this thread and waits for in flight
/// background work to finish. Timers that aren't due yet are not waited
/// for (see [`ThreadedDispatcher::run_until_idle`]). Scheduled frames are
/// delivered after each task poll and when there are no ready tasks, but
/// animations alone do not keep this method running.
pub fn run_until_idle(&self) {
let dispatcher = self
.background_executor
.dispatcher()
.as_threaded()
.expect("validated in BenchAppContext::build");
dispatcher.run_until_idle_with(|| {
let ran_tasks = dispatcher.run_ready_main_tasks_with(
|| true,
|| {
self.dispatch_pending_frames(|| true);
},
);
if !ran_tasks {
self.dispatch_pending_frames(|| true);
}
ran_tasks
});
}
/// Alternates draining queued work with GPUI update cycles until neither
/// makes progress, so state dropped by benchmark code is fully released.
///
/// Dropped entities are released only inside an update's effect flush, andView on GitHub (pinned to 9d272b0363)
Solutions
- Construct the platform with `gpui::bench_platform(headless_renderer_factory, text_system)` so its executors are backed by a `ThreadedDispatcher`.
- Ensure the `BenchAppContext` is created via its normal `build` constructor, which asserts the dispatcher is threaded (the panic message's 'validated in BenchAppContext::build' invariant).
- If you are writing a plain unit test rather than a benchmark, use `TestAppContext`/`gpui::test` instead of `BenchAppContext`.
- If a custom platform is required, implement `PlatformDispatcher::as_threaded` to return `Some(&ThreadedDispatcher)`.
Example fix
// before let platform = TestPlatform::new(); let cx = BenchAppContext::build(platform, ...); cx.run_until_idle(); // panics: not a ThreadedDispatcher // after let platform = gpui::bench_platform(None, text_system); let cx = BenchAppContext::build(platform, ...); cx.run_until_idle();
Defensive patterns
Strategy: validation
Validate before calling
if cx.background_executor.dispatcher().as_threaded().is_none() {
panic!("BenchAppContext requires a platform built with gpui::bench_platform");
} Type guard
fn is_bench_platform(executor: &BackgroundExecutor) -> bool {
executor.dispatcher().as_threaded().is_some()
} Prevention
- Always build benchmark apps on gpui::bench_platform.
- Never share a TestPlatform between #[gpui::test] tests and #[gpui::bench] benchmarks.
- Check dispatcher().as_threaded().is_some() once at harness setup.
- Use TestAppContext for unit tests instead of BenchAppContext.
When it happens
Trigger: Calling `run_until_idle()` (directly or via `settle()`, `add_empty_window()`, or `teardown()`) on a `BenchAppContext` whose platform was not constructed with `gpui::bench_platform` — e.g. constructing the context with a `TestPlatform` backed by a deterministic/single-threaded dispatcher, or swapping the executor after build.
Common situations: Reusing a test harness's `TestPlatform` for a benchmark instead of calling `bench_platform`; wiring a custom `Platform` implementation whose `dispatcher()` returns a non-threaded dispatcher; copy-pasting `TestAppContext` setup code into a `#[gpui::bench]` benchmark.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- blocking sender returned without value
- RealtimeAudio priority should use spawn_realtime, not dispat
- RealtimeAudio priority should use spawn_realtime, not dispat
- Device lost: {err}
- database not initialized
AI-assisted analysis of zed-industries/zed@9d272b0363 (2026-09-12).
Data as JSON: /api/errors/bc939c1feda39de6.
Report an issue: GitHub.