Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 22 additions & 11 deletions crates/agent_servers/src/acp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -669,9 +669,12 @@ const MINIMUM_SUPPORTED_VERSION: ProtocolVersion = ProtocolVersion::V1;
/// dispatch queue via `dispatch_tx`, where they are handled by the
/// `handle_*` functions on a GPUI context. The returned future drives the
/// connection and completes when the transport closes; callers are expected
/// to spawn it on a background executor and hold the task for the lifetime
/// of the connection. The `connection_tx` oneshot receives the
/// `ConnectionTo<Agent>` handle as soon as the builder runs its `main_fn`.
/// to poll it in the background and hold the task for the lifetime of the
/// connection. In unoptimized builds each inbound dispatch needs ~0.5 MiB
/// of stack, so poll it on a thread with room to spare (macOS GCD workers'
/// 512 KiB is not enough — see `AcpConnection::stdio`). The `connection_tx`
/// oneshot receives the `ConnectionTo<Agent>` handle as soon as the builder
/// runs its `main_fn`.
fn connect_client_future(
name: &'static str,
transport: impl agent_client_protocol::ConnectTo<Client> + 'static,
Expand Down Expand Up @@ -925,17 +928,25 @@ impl AcpConnection {
});

// `connect_client_future` installs the production handler set and
// hands us back both the connection-future (to run on a background
// executor) and a oneshot receiver that produces the
// `ConnectionTo<Agent>` once the transport handshake is ready.
// hands us back both the connection-future and a oneshot receiver
// that produces the `ConnectionTo<Agent>` once the transport
// handshake is ready. The future must be polled on a dedicated
// thread rather than via `background_spawn`: in unoptimized builds
// its dispatch chain needs ~0.5 MiB of stack per inbound message,
// which overflows the fixed 512 KiB stacks of the GCD workers that
// poll background tasks on macOS, crashing dev builds as soon as an
// agent sends its first message. See `spawn_dedicated` for the
// stack guarantee that makes the dedicated thread sufficient.
let (connection_tx, connection_rx) = futures::channel::oneshot::channel();
let connection_future =
connect_client_future("zed", transport, dispatch_tx.clone(), connection_tx);
let io_task = cx.background_spawn(async move {
if let Err(err) = connection_future.await {
log::error!("ACP connection error: {err}");
}
});
let io_task = cx
.background_executor()
.spawn_dedicated(move |_executor| async move {
if let Err(err) = connection_future.await {
log::error!("ACP connection error: {err}");
}
});

let connection_rx = async move {
connection_rx
Expand Down
21 changes: 21 additions & 0 deletions crates/gpui/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,27 @@ impl BackgroundExecutor {
self.inner.clone()
}

/// Spawn a closure on a fresh session pinned to its own [`SchedulerLocalExecutor`].
/// The closure runs on a new OS thread under the platform scheduler, or on
/// the test scheduler's loop in tests.
///
/// Prefer this over [`Self::spawn`] for futures whose polls need more stack
/// than shared background threads guarantee. Dedicated threads get the
/// standard library's default 2 MiB, while `spawn` polls futures on
/// whatever threads the platform dispatcher provides — on macOS those are
/// GCD workers whose stacks are fixed at 512 KiB by the kernel (see `PTH_DEFAULT_STACKSIZE` in
/// <https://github.com/apple-oss-distributions/libpthread/blob/42d026df5b07825070f60134b980a1ec2552dfee/kern/kern_internal.h#L154>),
/// the tightest background-stack budget of any platform.
#[track_caller]
pub fn spawn_dedicated<F, Fut>(&self, f: F) -> Task<Fut::Output>
where
F: FnOnce(SchedulerLocalExecutor) -> Fut + Send + 'static,
Fut: Future + 'static,
Fut::Output: Send + Sync + 'static,
{
self.inner.spawn_dedicated(f)
}

/// Enqueues the given future to be run to completion on a background thread.
#[track_caller]
pub fn spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
Expand Down
Loading