"Correct, and about 16 MiB per second at any size. Nobody noticed while every body was a few kilobytes of JSON."
A Small Pool That Expects You to Suspend
Swift concurrency runs tasks on a cooperative thread pool roughly the size of your CPU core count. "Cooperative" is the contract: a task is expected to suspend at await while it waits, giving its thread back. Code that blocks instead — a synchronous walk over a large directory tree, Thread.sleep, a semaphore wait, a synchronous network call — holds a pool thread hostage. Block a handful of them and unrelated tasks across the whole app stop making progress, with nothing in the logs to say why.
@concurrent and Task.detached do not change this. They choose which executor runs your code, and that executor is still the pool. Blocking work needs a thread that is allowed to block: a dispatch queue, or a dedicated thread, bridged back with a continuation.
The Server With Two Doors
The family's Swift HTTP listener, used by engines that run on the Mac, offers two handler doors on purpose. The async handler runs on a detached task — right for work that awaits. The blockingHandler runs on a concurrent dispatch queue — right for a sync engine that scans its data root with synchronous file-system calls. The choice is not style: put the scan behind the async door and it sits on a cooperative-pool thread for its whole duration.
Per-Element Overhead Is Real
The kit's hardened HTTP transport read response bodies like this: for try await byte in session.bytes(for: request), appending one UInt8 at a time while enforcing a size budget. Correct — and measured against a local file at about 16 MiB/s regardless of size, because every element costs an async next(). data(for:) moved the same bytes at over 1,000 MiB/s but gives up the mid-stream budget. The fix was a URLSessionDataDelegate: didReceive data arrives in chunks, appends once per chunk, and cancels the task the moment the running total would exceed the budget. Through the real transport over a loopback socket, 32 MiB then moved at about 1,237 MiB/s. Nobody noticed the slow loop for two weeks because every earlier consumer fetched small JSON; the first consumer to pull images found it.
A Main-Actor Test Body Blocks Its Own Callback
A Swift Testing test marked @MainActor verified a callback delivered on the main queue and timed out every time. The test body itself was the main-queue job, so the callback sat behind it, and spinning the run loop inside the test did not drain the dispatch queue. The fix was the test's shape, not the production code: make the test nonisolated, hop to the main actor only to arm and to read, and Task.sleep between polls so the main queue stays free.