% concurrency-debugging.tex — the cooperative pool's forward-progress contract, the
% three failures (blocked main actor, actor contention, pool starvation), the Swift
% Concurrency template (task states, task names in 26), Xcode 27 Swift Executors +
% Swift Task Collection + per Task/Actor/Executor "Profile" call tree, System Trace
% (thread states, syscalls, VM faults, priority graph in 27), Thread Performance
% Checker, TSan, LLDB `language swift task tree` (27), LIBDISPATCH_COOPERATIVE_POOL_STRICT.
% Basics (GCD vs await, actors, reentrancy, Sendable) are ios-swift/concurrency.tex —
% not repeated.
% Sources (primary, checked 2026-09-25): Xcode 26 + 27 release notes (Instruments,
% Debugger); WWDC22 110350 "Visualize and optimize Swift concurrency"; WWDC21 10254
% "Swift concurrency: Behind the scenes" (strict pool env var, via notes); Apple docs
% "Diagnosing performance issues early" (Thread Performance Checker) and "Diagnosing
% memory, thread, and crash issues early" (TSan platforms + overhead);
% research-swift-xcode-2026-09-25.md (SE-0461 / @concurrent, SE-0469 task names).
% Not printed (could not confirm): the full list of task-state names; the output
% format of `language swift task tree`.
% Build ONLY with: tools/print/print-sheet.py <this>.tex --dry-run
% @source: hiot monorepo, docs/school/sheets/debugging/concurrency-debugging.tex — the SOURCE OF TRUTH; a copy anywhere else (e.g. artur.gurgul.pro) is regenerated from it, never edited
% @labels: area=debugging kind=tooling level=deep platform=apple new=no round=market-2026-09-25 topic=concurrency,debugging
% @tags: cooperative-thread-pool, thread-starvation, actor-contention, main-actor, swift-concurrency-instrument, swift-executors, system-trace, thread-performance-checker, priority-inversion, tsan, task-tree, concurrent
\documentclass[8pt]{extarticle}
\usepackage{printup-sheet}
\usepackage{array}

\lstdefinelanguage{SwiftSheet}{
  morekeywords={func,let,var,return,async,await,in,nil,@concurrent,Task},
  alsoletter={@}, sensitive=true, morecomment=[l]{//}, morestring=[b]",
  literate={->}{{\hbox{-}\hbox{>}}}2 {==}{{\hbox{=}\hbox{=}}}2 {??}{{\hbox{?}\hbox{?}}}2}

\tikzset{
  lbl/.style={font=\scriptsize, text=black!75, inner sep=1pt},
  ln/.style={font=\scriptsize\bfseries, anchor=east, inner sep=1pt, align=right},
  run/.style={draw=sheetBlue, fill=sheetBlue!15, rounded corners=1pt, minimum height=4mm,
              font=\scriptsize, inner sep=1pt, anchor=west},
  blk/.style={run, draw=sheetRed, fill=sheetRed!20},
  act/.style={run, draw=sheetOrange, fill=sheetOrange!22},
  tk/.style={circle, draw=sheetGrey, fill=white, minimum size=3.6mm, inner sep=0pt,
             font=\tiny},
}

\begin{document}

\sheettitle{Debugging Swift Concurrency — pool, actors, executors}{debugging · memo}

\oneliner{Tasks run on the \textbf{cooperative thread pool} (\textasciitilde one
thread per core, \textbf{never grows}) or on the \textbf{main actor}'s main thread.
The runtime's contract is \textbf{forward progress}: \texttt{await} suspends and frees
the thread; a \emph{blocked} thread is simply lost. Three failures follow — a
\textbf{blocked main actor} (hang), \textbf{actor contention} (parallel code turned
serial), \textbf{pool starvation} (threads parked in waits). Concurrency instruments
show task/actor/executor \emph{state}; System Trace shows \emph{why} a thread is not running.}

\medskip
\noindent\begin{tikzpicture}[sheet]
  \foreach \y/\t in {3.0/Main Actor, 2.4/pool thread 1, 1.8/pool thread 2, 1.2/pool thread 3, 0.6/pool thread 4}
    { \node[ln] at (2.2,\y) {\t}; \draw[sheetGrey!40] (2.3,\y) -- (11.3,\y); }
  \node[ln] at (2.2,0.0) {runnable tasks};
  \draw[->, sheetGrey] (2.3,-0.4) -- (11.3,-0.4) node[lbl, right]{time};
  % main actor
  \node[run, minimum width=6mm] at (2.35,3.0) {UI};
  \node[run, minimum width=6mm] at (3.05,3.0) {UI};
  \node[blk, minimum width=58mm] at (3.8,3.0) {\texttt{@MainActor} \texttt{decode()} 400~ms — \textbf{hang}};
  \node[run, minimum width=6mm] at (9.75,3.0) {UI};
  % pool 1: healthy
  \foreach \x/\t in {2.35/A,3.3/B,4.25/C,5.2/D,6.15/E,7.1/F,8.05/G,9.0/H,9.95/I}
    \node[run, minimum width=8mm] at (\x,2.4) {\t};
  % pool 2 + 3: blocked
  \node[run, minimum width=6mm] at (2.35,1.8) {J};
  \node[blk, minimum width=80mm] at (3.1,1.8) {\texttt{semaphore.wait()} — Blocked, 0 \% CPU, thread lost};
  \node[run, minimum width=12mm] at (2.35,1.2) {K};
  \node[blk, minimum width=73mm] at (3.7,1.2) {sync I/O \texttt{Data(contentsOf:)} — Blocked in a syscall};
  % pool 4: actor
  \node[act, minimum width=45mm] at (2.35,0.6) {\texttt{ImageStore} actor job (resize inside)};
  \node[act, minimum width=9mm] at (6.95,0.6) {t5};
  \node[act, minimum width=9mm] at (7.95,0.6) {t6};
  % queue
  \foreach \x/\t in {4.0/t7,4.5/t8,5.0/t9,5.5/t10,6.0/t11} \node[tk] at (\x,0.0) {\t};
  \node[lbl, anchor=west, text=sheetRed] at (6.3,0.0) {ready, no free thread = \textbf{starvation}};
  \node[lbl, anchor=west, align=left, text=sheetOrange!85!black] at (8.95,0.6) {t5, t6 waited, enqueued\\on the actor = \textbf{contention}};
  % right: which tool shows it
  \begin{scope}[shift={(11.75,0)}]
    \node[font=\bfseries\scriptsize, anchor=west] at (0,3.3) {where you see it};
    \node[lbl, anchor=north west, text width=4.8cm, align=left] at (0,3.1)
      {\textcolor{sheetRed}{\textbf{hang}}: Hangs · Main Actor track (27) · Time Profiler on main\\[3pt]
       \textcolor{sheetRed}{\textbf{blocked threads}}: System Trace — state \emph{Blocked} + the wait syscall;
       Cooperative Thread Pool track (27); Time Profiler sees \emph{nothing}\\[3pt]
       \textcolor{sheetOrange!85!black}{\textbf{contention}}: Swift Actors / executor track —
       tasks \emph{enqueued}; narrative: ``waiting to get onto'' the actor\\[3pt]
       \textbf{stuck task}: task state \emph{waiting on a continuation} forever;
       LLDB \texttt{language swift task tree}};
  \end{scope}
\end{tikzpicture}

\begin{multicols}{2}

\section{How it works}
\begin{itemize}
  \item \textbf{Pool width} \textasciitilde CPU cores, fixed: no thread explosion, but
        no replacement for a blocked thread either. Unsafe in async code:
        \texttt{DispatchSemaphore}, \texttt{NSCondition}, \texttt{pthread\_cond},
        \texttt{group.wait()}, sync I/O. \texttt{Mutex} / \texttt{os\_unfair\_lock}:
        only short critical sections, never across an \texttt{await}.
  \item \textbf{Main actor} = the main thread's serial executor. Xcode 26 new projects
        default to \texttt{MainActor} isolation\unverified, and with \emph{Approachable Concurrency}
        a \texttt{nonisolated async} func runs on the \emph{caller's} actor (SE-0461) —
        mark CPU work \texttt{@concurrent} to leave main.
  \item \textbf{Actor} = serial executor: one job at a time, callers \emph{enqueue}.
        Keep only state mutation inside; do the heavy part nonisolated / \texttt{@concurrent}.
  \item \textbf{Swift Concurrency} template: \emph{Swift Tasks} + \emph{Swift Actors}.
        Running / Alive / Total Tasks; per-task state track; Task Forest
        (parent/child); creation backtrace; narrative (``waiting on Task X'').
        26: shows \texttt{Task(name:)} names (Swift 6.2).
  \item \textbf{Xcode 27}: \textbf{Swift Executors} instrument — tracks for the
        \emph{Cooperative Thread Pool}, the \emph{Main Actor} and each
        \texttt{TaskExecutor} / \texttt{SerialExecutor} (OS 27; older: ``Unknown
        executor''). \textbf{Swift Task Collection} tracks group tasks by name or
        creation site (lifetimes or states). \emph{Profile} detail = call tree of
        samples taken while a Task / Collection / Actor / Executor was
        \emph{Running} (record with Time or CPU Profiler).
  \item \textbf{System Trace}: thread states, syscalls, VM faults — one plot in 27;
        ←/→ follows the scheduling chain (who made this thread runnable); 27 graphs
        \textbf{thread priority over time} (starvation). Thread Activity (27):
        effective QoS per thread.
  \item \textbf{Thread Performance Checker} (on for Run by default): priority
        inversions + non-UI work on main (sync I/O, networking) $\to$ Issue navigator.
  \item \textbf{TSan}: same address, \textasciitilde same time, one a write (+ thread
        leaks, uninitialised mutexes). macOS or \textbf{Simulator only}; 5–10×
        memory, 2–20× slower. Swift 6 errors cover checked code — TSan catches
        \texttt{@unchecked Sendable}, unsafe pointers, C/ObjC.
  \item \textbf{LLDB}: 26 steps follow a Task across threads; 27 \texttt{language
        swift task tree} prints every task the debugger knows.
        \texttt{LIBDISPATCH\_COOPERATIVE\_POOL\_STRICT=1} (scheme env): pool width
        1, so a blocking wait deadlocks \emph{now}, in dev.
\end{itemize}

\columnbreak

\section{Example — suspend, don't block}
\begin{lstlisting}[language=SwiftSheet]
// BAD: parks a pool thread until the callback fires
func thumb(_ u: URL) async -> UIImage? {
  let sem = DispatchSemaphore(value: 0); var img: UIImage?
  legacyLoad(u) { img = $0; sem.signal() }
  sem.wait()                        // Blocked, thread lost
  return img }
// GOOD: the task suspends; resume exactly once
func thumb(_ u: URL) async -> UIImage? {
  await withCheckedContinuation { c in
    legacyLoad(u) { c.resume(returning: $0) } } }
// CPU work off the caller's actor (6.2); a named task
@concurrent func decode(_ d: Data) async -> UIImage? {
  UIImage(data: d) }
Task(name: "thumb") { await show(thumb(url)) }
\end{lstlisting}

\section{Symptom $\to$ evidence $\to$ fix}
{\footnotesize
\begin{tabular}{@{}p{1.75cm}p{3.35cm}p{2.4cm}@{}}
\toprule
\textbf{symptom} & \textbf{evidence} & \textbf{fix} \\
\midrule
UI frozen & one long Main Actor job & \texttt{@concurrent} \\
slow, CPU idle & pool threads \emph{Blocked} (System Trace) & \texttt{await}, continuation \\
parallel = serial & tasks \emph{enqueued} on one actor & shrink / shard actor \\
never finishes & stuck waiting on a continuation & resume on every path \\
inversion & TPC issue; priority graph (27) & no wait on lower QoS \\
\bottomrule
\end{tabular}}

\section{Interview traps}
\begin{itemize}
  \trap{``Spawn more tasks'' — the pool never grows; a blocked thread stays lost.}
  \trap{\texttt{nonisolated async} $\neq$ background under SE-0461: it inherits the
        caller's actor. \texttt{@concurrent} means ``off it''.}
  \trap{\texttt{Task.detached} to ``get off main'' drops priority and task-locals.}
  \trap{TSan: not on a device. Swift 6: does not check \texttt{@unchecked}.}
\end{itemize}

\section{Remember}
\textbf{Suspend, never block — the pool never grows.} Main actor = main thread; one
actor = one lane.

\section{Likely questions}
\begin{enumerate}
  \item Semaphore in async code? — parks a pool thread: starvation.
  \item Actor contention? — actor/executor track: time \emph{enqueued}.
  \item Priority inversion? — high QoS waits on low QoS; TPC flags it.
  \item Stuck task? — a continuation never resumed.
\end{enumerate}

\end{multicols}

\noindent{\footnotesize\color{sheetGrey}\textit{Related:} concurrency (GCD vs await,
actors, reentrancy) · instruments-performance · time-profiler-cpu-deep ·
swiftui-instrument-hitches-hangs · memory-debugging-deep}

\end{document}
