# scheduler: Add spawn_dedicated for single-threaded actors with \!Send state \(\#57609\) · gitcafe/zed

[View on GitCafe](https://git.cafe/gitcafe/zed/commit/c30d18b10d5aa64212f75ef4c8efd5c63aa35b35)

Repository: [gitcafe/zed](https://git.cafe/gitcafe/zed)

Visibility: public

Requested revision: c30d18b10d5aa64212f75ef4c8efd5c63aa35b35

Requested commit: c30d18b10d5aa64212f75ef4c8efd5c63aa35b35

Commit: c30d18b10d5aa64212f75ef4c8efd5c63aa35b35

Tree: f1693926faa2d13ce237b0b6a8a71a89bed91bcb

Author: Nathan Sobo

Committer: GitHub

## Message

```
scheduler: Add spawn_dedicated for single-threaded actors with !Send state (#57609)

Adds `scheduler::spawn_dedicated_thread` (and inherent `spawn_dedicated`
methods on `PlatformScheduler` and `TestScheduler`) so single-threaded
actors that own `!Send` state can run on their own OS thread and freely
do blocking I/O without disturbing any other executor.

### Why

A single-threaded actor that needs to do blocking syscalls is currently
stuck: it can't run on the shared foreground executor (blocking would
stall every other foreground session), and it can't move to the
background pool because its state isn't `Send`. `spawn_dedicated` gives
each such actor its own thread and its own `LocalExecutor`, while still
participating in the same testable scheduler infrastructure as
everything else.

### Shape

- `pub fn spawn_dedicated_thread(session_id, scheduler, f) -> Task<_>`
in `scheduler`. Owns the OS thread, the per-session runnable channel,
and the `LocalExecutor` setup.
- Inherent `spawn_dedicated` on `PlatformScheduler` (allocates its own
`SessionId`, delegates to the free function).
- Inherent `spawn_dedicated` on `TestScheduler` (no real thread — runs
as a fresh local session driven by the test scheduler's run loop, so
determinism under `many` is preserved).
- Renames `Scheduler::schedule_foreground` → `schedule_local` and
`scheduler::ForegroundExecutor` → `scheduler::LocalExecutor` to reflect
that these are session-pinned queues rather than "the main thread" (a
dedicated session runs on its own thread). GPUI's wrapper
`gpui::ForegroundExecutor` and the `foreground_executor` field/method
names are unchanged to keep blast radius small.
- `LocalExecutor::new` now takes an explicit dispatch closure, so the
routing decision (default session, dedicated thread, or something else)
lives at the construction site.

### Tests

- `TestScheduler` side: round-trip, `!Send` future, `Send` closure
capturing shared state, inner `executor.spawn`, determinism under `many`
seeds, drop-cancels-future, detached child runs after root completes.
- `PlatformScheduler` side: real separate thread (blocking syscalls
don't stall the test), `!Send` future output, drop-cancels-future,
thread tears down after work completes, detached child outlives root.

cc @as-cii

Release Notes:

- N/A

---------

Co-authored-by: Antonio Scandurra <me@as-cii.com>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
```

## Parents

- [18051ab3999e4f5604be372d2d159d45d63abc70](https://git.cafe/gitcafe/zed/commit/18051ab3999e4f5604be372d2d159d45d63abc70?format=markdown)

[Source at this commit](https://git.cafe/gitcafe/zed/tree/c30d18b10d5aa64212f75ef4c8efd5c63aa35b35?format=markdown)
