<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Alex Day</title>
    <description>The latest articles on DEV Community by Alex Day (@silvern47).</description>
    <link>https://dev.to/silvern47</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1112348%2F3303211a-d641-489a-99f2-1943a49f622a.jpg</url>
      <title>DEV Community: Alex Day</title>
      <link>https://dev.to/silvern47</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/silvern47"/>
    <language>en</language>
    <item>
      <title>Proximity based word correction on keyboard</title>
      <dc:creator>Alex Day</dc:creator>
      <pubDate>Thu, 27 Aug 2026 17:50:10 +0000</pubDate>
      <link>https://dev.to/silvern47/minor-improvements-on-keyboard-4bf2</link>
      <guid>https://dev.to/silvern47/minor-improvements-on-keyboard-4bf2</guid>
      <description>&lt;p&gt;This is from the second phase of &lt;a href="https://dev.to/silvern47/building-next-word-prediction-for-a-keyboard-app-pipeline-smoothing-algorithms-and-vocabulary-1ng9"&gt;CodeKeyBoard Series&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While typing, I realized, word correction, is not just about LV or Symspell or BK-Trees. I mean there are optimizations and choices of data-structures but there is something else.&lt;/p&gt;

&lt;p&gt;In a T9 key prediction, a set of numbers map to a possible words, similarly, in a bigger keyboard, to accommodate for fat-fingers, we need to consider the proximity of each character - the keyboard is not &lt;code&gt;ABCDE&lt;/code&gt; its &lt;code&gt;QWERTY&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;QWERTY adjacency map (physical key neighbors) would somewhat look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;q: a s w        r: d e f g t      u: h i j k y
w: a d e q s    t: f g h r y      i: j k l o u
e: d f r s w    y: g h j t u      o: i k l p
             a: q s w x z   h: b g j m n t u y
             s: a c d e q w x z   j: h i k m n u y
             d: c e f r s v w x   k: i j l m o u
             f: b c d e g r t v   l: i k o p
             g: b f h n r t v y
             z: a s x    b: c f g h n v
             x: a c d s z    n: b g h j m
             c: d f s v x    m: h j k n
             v: b c d f g
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So instead of jumping straight to pre-generating the deletes for Symspell, this is an easier addition that can be made on top of LV.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsdzdz4gjztsry5ensn1s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsdzdz4gjztsry5ensn1s.png" alt=" " width="754" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we can see, "search" come up, because the pattern of strokes match&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FUTO Swipe models&lt;/li&gt;
&lt;li&gt;NLnet: Gesture Typing for AOSP-derived Keyboards&lt;/li&gt;
&lt;li&gt;Kristensson &amp;amp; Zhai, IUI 2005 - "Relaxing stylus typing precision by geometric pattern matching"&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>Crisp-Engine: *sodic memory for ai agents</title>
      <dc:creator>Alex Day</dc:creator>
      <pubDate>Mon, 24 Aug 2026 14:59:38 +0000</pubDate>
      <link>https://dev.to/silvern47/crisp-engine-sodic-memory-for-ai-agents-1opj</link>
      <guid>https://dev.to/silvern47/crisp-engine-sodic-memory-for-ai-agents-1opj</guid>
      <description>&lt;p&gt;I got tired of explaining context to my AI coding assistant every single session.&lt;/p&gt;

&lt;p&gt;So I built Crisp Engine - an episodic memory layer for AI coding agents.&lt;/p&gt;

&lt;p&gt;Not RAG. Not a vector store wrapper. A structured memory system that watches what your agent actually does - every file edit, git commit, bash failure - and builds a record that persists across sessions.&lt;/p&gt;

&lt;p&gt;Crisp remembers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what you've tried and why it didn't work&lt;/li&gt;
&lt;li&gt;patterns in your frustration (yes, really)&lt;/li&gt;
&lt;li&gt;what files you keep touching and what broke near them&lt;/li&gt;
&lt;li&gt;lessons extracted from your own commit messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture is agent-agnostic. Claude Code, OpenCode, Pi - same memory layer, adapter pattern handles the differences.&lt;/p&gt;

&lt;p&gt;implements the full academic memory stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CoALA 4-layer taxonomy (working → episodic → semantic → procedural)&lt;/li&gt;
&lt;li&gt;MemGPT-style hierarchical paging (L0 raw → L1 summaries → L2 clusters → L3 arcs)&lt;/li&gt;
&lt;li&gt;Ebbinghaus decay per layer, access-frequency promotion, conflict detection&lt;/li&gt;
&lt;li&gt;A-MEM Zettelkasten links between episodes (similar/caused/contradicts/corrected_by)&lt;/li&gt;
&lt;li&gt;5-layer zoom retrieval with reranking (vector + recency + importance + access freq)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interesting parts:&lt;/p&gt;

&lt;p&gt;-&amp;gt; Tool Watchers: pluggable observers that turn tool calls into memory episodes. Git push rejected? Frustration score goes up. Hard reset? Flagged as destructive. Write your own watcher in ~30 lines.&lt;/p&gt;

&lt;p&gt;-&amp;gt; Instinct distillation: repeated patterns graduate into named instincts. High-confidence instincts can generate new watchers automatically.&lt;/p&gt;

&lt;p&gt;-&amp;gt; Embedding fallback chain: Ollama flaky? Crisp falls through to HuggingFace, then word2vec. Never silently drops your data.&lt;/p&gt;

&lt;p&gt;Still building. Open to collaborators who think agents should get smarter the longer you work with them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/ikouchiha47/crisp-engine" rel="noopener noreferrer"&gt;repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;N.B. I don't know how much its working, still measuring.&lt;br&gt;
N.B. Before you ask, how its different from hernia-agent, its very different, the last thing I want in life rn is another md file.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>architecture</category>
      <category>llm</category>
    </item>
    <item>
      <title>Streamux: Http2 prioritization and scheduling engine</title>
      <dc:creator>Alex Day</dc:creator>
      <pubDate>Sun, 23 Aug 2026 13:00:42 +0000</pubDate>
      <link>https://dev.to/silvern47/streamux-full-rfc-9113-in-go-with-c-abi-3hie</link>
      <guid>https://dev.to/silvern47/streamux-full-rfc-9113-in-go-with-c-abi-3hie</guid>
      <description>&lt;p&gt;Spent the last few weeks building an HTTP/2 server from scratch in Go.&lt;/p&gt;

&lt;p&gt;Full RFC 9113 - framing, HPACK, stream state machine, flow control, TLS enforcement, 145/146 h2spec tests passing.&lt;/p&gt;

&lt;p&gt;The fun part was RFC 9218 stream priorities. Browsers already send priority: u=N on every fetch. Most servers throw it away. I built a pluggable write scheduler that actually uses it - deficit round-robin across urgency levels, so hero images and nav get bandwidth before analytics pixels, on every flow-control window round.&lt;/p&gt;

&lt;p&gt;50 concurrent streams, 16 KiB window: DRR finishes 40% faster than round-robin. Above-the-fold content lands first, tracking junk last. Exactly how it should work.&lt;/p&gt;

&lt;p&gt;Also exports a C ABI (.so) so non-Go services can use it.&lt;/p&gt;

</description>
      <category>go</category>
      <category>http</category>
      <category>network</category>
    </item>
    <item>
      <title>Building next-word prediction for a keyboard app - pipeline, smoothing algorithms, and vocabulary tradeoffs</title>
      <dc:creator>Alex Day</dc:creator>
      <pubDate>Fri, 21 Aug 2026 15:55:52 +0000</pubDate>
      <link>https://dev.to/silvern47/building-next-word-prediction-for-a-keyboard-app-pipeline-smoothing-algorithms-and-vocabulary-1ng9</link>
      <guid>https://dev.to/silvern47/building-next-word-prediction-for-a-keyboard-app-pipeline-smoothing-algorithms-and-vocabulary-1ng9</guid>
      <description>&lt;p&gt;This is a summary of three recent posts from my CodeKeyboard rebuild series. CodeKeyboard is an open-source Android keyboard for programmers — layers, modifier keys, fuzzy autocorrect, and now a real statistical next-word prediction engine.&lt;/p&gt;

&lt;p&gt;The prediction model&lt;/p&gt;

&lt;p&gt;The keyboard trains a trigram language model on the Coursera SwiftKey corpus: 583MB of blogs, news, and Twitter text, 4.27 million lines. A trigram model stores, for every two-word context ("I am"), a ranked list of likely next words ("going", "a", "sure"...). At typing time, the keyboard looks up whatever the last two committed words were and surfaces the top candidates.&lt;/p&gt;

&lt;p&gt;Three algorithm variants were built:&lt;/p&gt;

&lt;p&gt;Kneser-Ney (KN) - instead of counting how often a word appears, it counts how many different contexts a word appears in. The word "Francisco" appears frequently in the corpus, but almost always after "San". KN scores it low as a generic next-word guess because it continues so few contexts. Good for cold-start guessing; harder to tune under size constraints.&lt;/p&gt;

&lt;p&gt;Katz backoff - uses Good-Turing discounting to redistribute probability mass toward words the model has seen less of. More stable when the model gets pruned aggressively to fit on a phone.&lt;/p&gt;

&lt;p&gt;SwiftKey WDP - takes the Katz model and asks of every stored next-word candidate: "does knowing the two-word context actually change this word's odds compared to the one-word context alone?" Candidates that merely restate the simpler model get dropped. Result: 31% smaller file, 99.2% top-1 agreement with Katz. This one ships.&lt;/p&gt;

&lt;p&gt;The pipeline problem&lt;/p&gt;

&lt;p&gt;The first full-corpus AWS run consumed all memory and produced nothing. The second attempt ran for over two hours and was killed manually.&lt;/p&gt;

&lt;p&gt;The fix: switched to a streaming SQLite pipeline — counts flow through a rolling merge rather than loading into RAM, intermediate results checkpoint to disk, and the whole build now completes in 11 minutes on a spot t3.xlarge.&lt;/p&gt;

&lt;p&gt;The vocabulary question&lt;/p&gt;

&lt;p&gt;The trained model has a 427,651-word vocabulary. 85% of those words appear fewer than 15 times across 85 million tokens. Zipf's law in action.&lt;/p&gt;

&lt;p&gt;Four vocabulary caps were tested: 16K, 32K, 64K, 128K words. The measurement: how often does the right next word fall outside the cap? At 64K, coverage reaches 99.3% of real next-word targets. Above that,&lt;br&gt;
gains are marginal. The 64K cap ships.&lt;/p&gt;

&lt;p&gt;The result&lt;/p&gt;

&lt;p&gt;Everything — vocabulary, character trie, bigram/trigram follower lists, phrase data — comy .cklm file. 22MB. Loaded via memory-map at startup. Three separate JSON files are gone.APK: 302MB down to 94MB.&lt;/p&gt;

&lt;p&gt;Full write-ups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ikouchiha47.github.io/keyboard/08-ngram-pipeline-optimization.html" rel="noopener noreferrer"&gt;Part 8&lt;/a&gt; - Pipeline&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ikouchiha47.github.io/keyboard/09-ngram-smoothing-katz-swiftkey-wdp.html" rel="noopener noreferrer"&gt;Part 9&lt;/a&gt; - Smoothing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ikouchiha47.github.io/keyboard/10-vocab-tail-analysis.html" rel="noopener noreferrer"&gt;Part 10&lt;/a&gt; - Vocabulary&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>nlp</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>A Quick rundown on concurrency and garbage collection</title>
      <dc:creator>Alex Day</dc:creator>
      <pubDate>Wed, 12 Aug 2026 17:14:08 +0000</pubDate>
      <link>https://dev.to/silvern47/a-quick-rundown-on-concurrency-and-garbage-collection-73k</link>
      <guid>https://dev.to/silvern47/a-quick-rundown-on-concurrency-and-garbage-collection-73k</guid>
      <description>&lt;h2&gt;
  
  
  Abstract
&lt;/h2&gt;

&lt;p&gt;Three languages — Go, Kotlin, and Erlang/Elixir (running on BEAM) — solve the&lt;br&gt;
same problem (run many logical tasks on few OS threads) with three different&lt;br&gt;
answers to one question: &lt;strong&gt;who controls the switch between tasks, and what&lt;br&gt;
does that controller need to know to do it safely?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer to that question determines everything downstream: whether the&lt;br&gt;
model is cooperative or preemptive, whether GC pauses one thread or the whole&lt;br&gt;
process, and whether a crash is contained or catastrophic.&lt;/p&gt;

&lt;p&gt;This document derives each model from its constraints rather than describing&lt;br&gt;
it as a list of features. Each section ends with a checkpoint question you&lt;br&gt;
should be able to answer before moving to the next section.&lt;/p&gt;


&lt;h2&gt;
  
  
  Background: the problem all three are solving
&lt;/h2&gt;

&lt;p&gt;A CPU core runs one instruction stream at a time. OS threads are the&lt;br&gt;
kernel's abstraction for time-slicing a core across many instruction&lt;br&gt;
streams, but they are expensive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;~8MB stack per thread (Linux default)&lt;/li&gt;
&lt;li&gt;A context switch saves/restores the full register file and disturbs the
cache and TLB&lt;/li&gt;
&lt;li&gt;10,000 OS threads means gigabytes of stack space before any work is done&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So every runtime that wants cheap concurrency builds an &lt;strong&gt;M:N scheduler&lt;/strong&gt;:&lt;br&gt;
M logical tasks multiplexed onto N OS threads (typically N ≈ number of&lt;br&gt;
cores). The three systems below are three different M:N schedulers, and&lt;br&gt;
they differ because they made different decisions about who owns the&lt;br&gt;
switching logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Checkpoint:&lt;/strong&gt; before continuing, state in one sentence why an OS thread is&lt;br&gt;
too expensive to use one-per-logical-task at scale.&lt;/p&gt;


&lt;h2&gt;
  
  
  Part 1 — Go
&lt;/h2&gt;

&lt;p&gt;Sources read directly for this section (not paraphrased from memory):&lt;br&gt;
&lt;code&gt;src/runtime/preempt.go&lt;/code&gt;, &lt;code&gt;src/runtime/signal_unix.go&lt;/code&gt;, &lt;code&gt;src/runtime/proc.go&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;src/runtime/mgc.go&lt;/code&gt; — golang/go, master branch, fetched from&lt;br&gt;
raw.githubusercontent.com.&lt;/p&gt;
&lt;h3&gt;
  
  
  1.1 Key concept: this is CSP, not fork-join
&lt;/h3&gt;

&lt;p&gt;Go's concurrency model is explicitly an implementation of Hoare's&lt;br&gt;
&lt;strong&gt;Communicating Sequential Processes (CSP, 1978)&lt;/strong&gt; — independent sequential&lt;br&gt;
processes that interact &lt;em&gt;only&lt;/em&gt; through message passing over channels, not&lt;br&gt;
shared mutable state accessed via locks. This is a design lineage, stated&lt;br&gt;
directly in Go's own materials: "Don't communicate by sharing memory;&lt;br&gt;
share memory by communicating."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where CSP itself came from.&lt;/strong&gt; Tony Hoare published "Communicating&lt;br&gt;
Sequential Processes" in &lt;em&gt;Communications of the ACM&lt;/em&gt;, 1978. The problem he&lt;br&gt;
was working on wasn't concurrency in the modern web-service sense — it was&lt;br&gt;
correctness of concurrent programs at a time when shared-variable&lt;br&gt;
concurrency (semaphores, monitors) was the dominant model and was proving&lt;br&gt;
extremely hard to reason about formally: with shared mutable state, the&lt;br&gt;
number of possible interleavings of two processes explodes, and proving a&lt;br&gt;
program correct meant proving it correct under &lt;em&gt;all&lt;/em&gt; of them.&lt;/p&gt;

&lt;p&gt;Hoare's move was to make the &lt;em&gt;only&lt;/em&gt; interaction between processes an&lt;br&gt;
explicit, synchronous, named event — a process names who it's sending&lt;br&gt;
to/receiving from, and the send/receive pair is the entire synchronization&lt;br&gt;
primitive, with no separate lock needed. This has a real mathematical&lt;br&gt;
payoff: because processes share nothing, you can reason about each process&lt;br&gt;
in isolation and about the communication events between them as a&lt;br&gt;
separate, much smaller problem — closer to algebra than to exhaustive&lt;br&gt;
case analysis of shared-memory interleavings. CSP was formalized further&lt;br&gt;
into a full process algebra in Hoare's own later work and independently&lt;br&gt;
alongside Robin Milner's CCS (Calculus of Communicating Systems, also late&lt;br&gt;
1970s) — the two are usually cited together as the origin of process&lt;br&gt;
algebras generally.&lt;/p&gt;

&lt;p&gt;Go's designers (Rob Pike, in particular, who had earlier worked on&lt;br&gt;
Newsqueak and Alef — direct experimental predecessors that already used&lt;br&gt;
CSP-style channels) took the &lt;em&gt;communication primitive&lt;/em&gt; from CSP —&lt;br&gt;
synchronous, named-channel message passing — without adopting Hoare's full&lt;br&gt;
formal process algebra or his original synchronous-only restriction (Go's&lt;br&gt;
buffered channels allow asynchronous sends up to the buffer size, which&lt;br&gt;
Hoare's original calculus didn't have). So "Go implements CSP" is accurate&lt;br&gt;
at the level of the core idea — channels as the unit of synchronization,&lt;br&gt;
not locks — and imprecise if taken to mean Go implements the full formal&lt;br&gt;
calculus.&lt;/p&gt;

&lt;p&gt;This matters because it's easy to conflate with two other models that are&lt;br&gt;
&lt;em&gt;not&lt;/em&gt; what Go does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fork-join&lt;/strong&gt; (Java's &lt;code&gt;ForkJoinPool&lt;/code&gt;, Cilk, OpenMP): a task explicitly
splits into subtasks, waits for all of them, then joins. The parallelism
is structured around a single computation's divide-and-conquer shape.
Go has nothing built into the language for this — you'd hand-roll it
with a &lt;code&gt;sync.WaitGroup&lt;/code&gt;. Goroutines are not spawned with an implicit
join; &lt;code&gt;go f()&lt;/code&gt; returns immediately and nothing waits for it unless you
add that synchronization yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared-memory threading with locks&lt;/strong&gt; (raw pthreads, Java &lt;code&gt;synchronized&lt;/code&gt;):
the default coordination primitive is a shared address space guarded by
mutual exclusion. Go supports this too (&lt;code&gt;sync.Mutex&lt;/code&gt; exists and is used
heavily inside the runtime itself), but it's not the idiomatic surface
the language pushes you toward.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Channels (&lt;code&gt;chan&lt;/code&gt;) are the CSP primitive: a goroutine sends a value into a&lt;br&gt;
channel, another receives it, and the transfer itself is the&lt;br&gt;
synchronization point — no separate lock is needed for that handoff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Checkpoint:&lt;/strong&gt; if goroutines don't implicitly join, what actually&lt;br&gt;
guarantees a &lt;code&gt;go f()&lt;/code&gt; call's side effects are visible before &lt;code&gt;main()&lt;/code&gt;&lt;br&gt;
returns? (Answer: nothing, by default — this is why programs that don't&lt;br&gt;
explicitly wait via a channel or &lt;code&gt;WaitGroup&lt;/code&gt; can exit before spawned&lt;br&gt;
goroutines finish; it's a common bug source, not a language guarantee.)&lt;/p&gt;
&lt;h3&gt;
  
  
  1.2 The GMP scheduler
&lt;/h3&gt;

&lt;p&gt;From &lt;code&gt;proc.go&lt;/code&gt;'s own top-of-file doc comment (line ~24 onward):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The scheduler's job is to distribute ready-to-run goroutines over worker&lt;br&gt;
threads.&lt;br&gt;
G - goroutine.&lt;br&gt;
M - worker thread, or machine.&lt;br&gt;
P - processor, a resource that is required to execute Go code. M must&lt;br&gt;
have an associated P to execute Go code, however it can be blocked or in&lt;br&gt;
a syscall w/o an associated P.&lt;br&gt;
Design doc at &lt;a href="https://golang.org/s/go11sched" rel="noopener noreferrer"&gt;https://golang.org/s/go11sched&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the go11sched design (Dmitry Vyukov, 2012) — the M:N:P model exists&lt;br&gt;
specifically to solve the problem of per-P local run queues: without P as a&lt;br&gt;
separate scheduling resource, every M contending for work would need to hit&lt;br&gt;
a global queue, which doesn't scale past a few cores. Each P owns a local&lt;br&gt;
run queue; an M must acquire a P to run Go code at all, which is why a&lt;br&gt;
goroutine blocked in a syscall releases its P for another M to pick up&lt;br&gt;
(&lt;code&gt;handoffp&lt;/code&gt; in &lt;code&gt;proc.go&lt;/code&gt;) rather than leaving a core idle.&lt;/p&gt;
&lt;h3&gt;
  
  
  1.3 Preemption: how it evolved, and why GC is the reason it changed at all
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Go ≤1.10 — cooperative, at function prologues.&lt;/strong&gt; Every function call&lt;br&gt;
checked a "please yield" flag (implemented by poisoning the stack-bound&lt;br&gt;
check, per &lt;code&gt;preempt.go&lt;/code&gt;'s header comment: "Synchronous safe-points are&lt;br&gt;
implemented by overloading the stack bound check in function prologues").&lt;br&gt;
A goroutine with no function calls in its loop body (&lt;code&gt;for {}&lt;/code&gt;) never&lt;br&gt;
triggered that check and could never be preempted this way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this is a GC problem, specifically, and not just a scheduling&lt;br&gt;
fairness problem:&lt;/strong&gt; &lt;code&gt;mgc.go&lt;/code&gt; calls &lt;code&gt;stopTheWorldWithSema&lt;/code&gt; at both mark&lt;br&gt;
termination and sweep termination (&lt;code&gt;gcMarkTermination&lt;/code&gt;, &lt;code&gt;gcStart&lt;/code&gt; —&lt;br&gt;
&lt;code&gt;mgc.go&lt;/code&gt; lines ~835, ~1066 in the current source). STW literally means&lt;br&gt;
every goroutine must reach a state the GC considers safe before the GC&lt;br&gt;
phase can proceed — the mark/sweep transition cannot begin with even one&lt;br&gt;
goroutine still running arbitrary code, because the GC needs a globally&lt;br&gt;
consistent view of what's reachable. A goroutine stuck in a tight loop&lt;br&gt;
with no function calls blocked this indefinitely. This was the actual,&lt;br&gt;
measured production problem that motivated the 2019 proposal&lt;br&gt;
(golang/proposal &lt;code&gt;24543-non-cooperative-preemption.md&lt;/code&gt;) — not a general&lt;br&gt;
desire for "fair" scheduling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Go 1.14 — non-cooperative, signal-based.&lt;/strong&gt; &lt;code&gt;preempt.go&lt;/code&gt;'s own comment&lt;br&gt;
distinguishes three safe-point categories precisely:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Blocked safe-points&lt;/strong&gt; — a goroutine descheduled, blocked on sync, or
in a syscall. Cheap: the runtime already has full knowledge of its stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Synchronous safe-points&lt;/strong&gt; — a running goroutine voluntarily checks for
a pending preemption request (the stack-bound-check trick above).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asynchronous safe-points&lt;/strong&gt; — &lt;em&gt;any&lt;/em&gt; instruction in user code where a
conservative stack/register scan can still find all roots. The runtime
can stop a goroutine here using a signal, without the goroutine's
cooperation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;signal_unix.go&lt;/code&gt; (lines 44–74) states the SIGURG rationale directly, as&lt;br&gt;
four numbered criteria the signal had to satisfy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Must be a signal debuggers pass through by default (on Linux: &lt;code&gt;SIGALRM&lt;/code&gt;,
&lt;code&gt;SIGURG&lt;/code&gt;, &lt;code&gt;SIGCHLD&lt;/code&gt;, &lt;code&gt;SIGIO&lt;/code&gt;, &lt;code&gt;SIGVTALRM&lt;/code&gt;, &lt;code&gt;SIGPROF&lt;/code&gt;, &lt;code&gt;SIGWINCH&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Must not be claimed internally by libc in mixed Go/C binaries (rules out
&lt;code&gt;SIGCANCEL&lt;/code&gt;, &lt;code&gt;SIGSETXID&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Must be safe to receive spuriously — ruling out &lt;code&gt;SIGALRM&lt;/code&gt; (ambiguous
cause) and &lt;code&gt;SIGUSR1&lt;/code&gt;/&lt;code&gt;SIGUSR2&lt;/code&gt; (commonly used by applications for real
things).&lt;/li&gt;
&lt;li&gt;Must exist on platforms without real-time signals (rules out macOS's
missing RT signal range).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;SIGURG&lt;/code&gt; won because out-of-band TCP data is essentially unused in&lt;br&gt;
practice, the signal doesn't even report which socket triggered it (making&lt;br&gt;
it nearly useless for its literal purpose), and any correctly-written&lt;br&gt;
application already has to tolerate a spurious &lt;code&gt;SIGURG&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The mechanism, traced through the actual call path:&lt;br&gt;
&lt;code&gt;suspendG&lt;/code&gt; (&lt;code&gt;preempt.go&lt;/code&gt;) drives a goroutine toward suspension — if it's&lt;br&gt;
&lt;code&gt;_Grunning&lt;/code&gt;, it sets &lt;code&gt;gp.preemptStop&lt;/code&gt;/&lt;code&gt;gp.preempt&lt;/code&gt; and calls &lt;code&gt;preemptM&lt;/code&gt;,&lt;br&gt;
which calls &lt;code&gt;signalM(mp, sigPreempt)&lt;/code&gt; (&lt;code&gt;signal_unix.go&lt;/code&gt; line ~386) to&lt;br&gt;
deliver &lt;code&gt;SIGURG&lt;/code&gt; to that specific OS thread. The signal handler&lt;br&gt;
(&lt;code&gt;doSigPreempt&lt;/code&gt;, &lt;code&gt;signal_unix.go&lt;/code&gt; line ~342) inspects the interrupted PC&lt;br&gt;
and only acts if it lands on a recognized async safe-point; if not, it&lt;br&gt;
leaves the goroutine to continue and retries. Once accepted, the handler&lt;br&gt;
rewrites the signal context so execution resumes at &lt;code&gt;asyncPreempt&lt;/code&gt;, which&lt;br&gt;
spills all potentially-pointer-holding registers to the stack before&lt;br&gt;
handing off to the scheduler — this is what makes the stack scannable by&lt;br&gt;
the GC afterward.&lt;/p&gt;

&lt;p&gt;An intermediate fix — inserting checks at loop back-edges so tight loops&lt;br&gt;
without function calls would still hit synchronous safe-points — was&lt;br&gt;
measured and &lt;strong&gt;rejected&lt;/strong&gt;: 7.8% throughput regression, considered too&lt;br&gt;
expensive to pay unconditionally across all Go programs just to close this&lt;br&gt;
gap. Signal-based async preemption was strictly cheaper because it costs&lt;br&gt;
nothing until actually invoked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tracing it through a concrete example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="c"&gt;// spin forever, no function calls&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Go ≤1.10, &lt;code&gt;runtime.GC()&lt;/code&gt; here would never return. &lt;code&gt;runtime.GC()&lt;/code&gt; forces&lt;br&gt;
a stop-the-world GC cycle, which calls &lt;code&gt;stopTheWorldWithSema&lt;/code&gt; — every&lt;br&gt;
goroutine, including the spinning one, must reach a safe-point first. The&lt;br&gt;
spinning goroutine calls no functions, so it never hits the stack-bound&lt;br&gt;
check the old synchronous mechanism relied on. It spins forever; the GC&lt;br&gt;
waits forever; the program hangs.&lt;/p&gt;

&lt;p&gt;On Go ≥1.14, the same program's GC call succeeds. &lt;code&gt;stopTheWorldWithSema&lt;/code&gt;&lt;br&gt;
calls &lt;code&gt;suspendG&lt;/code&gt; on the spinning goroutine; since it's &lt;code&gt;_Grunning&lt;/code&gt; with no&lt;br&gt;
pending suspend, &lt;code&gt;suspendG&lt;/code&gt; calls &lt;code&gt;preemptM&lt;/code&gt;, which sends &lt;code&gt;SIGURG&lt;/code&gt; to the&lt;br&gt;
OS thread running it. The signal interrupts the &lt;code&gt;for {}&lt;/code&gt; loop mid-flight —&lt;br&gt;
at literally any instruction, since the compiler's stack maps cover every&lt;br&gt;
async safe-point in the loop body, even one with zero function calls. The&lt;br&gt;
signal handler rewrites execution to resume at &lt;code&gt;asyncPreempt&lt;/code&gt;, which&lt;br&gt;
spills registers to the stack, and the goroutine parks. GC proceeds. This&lt;br&gt;
exact scenario — an infinite loop with no calls — is the textbook case the&lt;br&gt;
2019 proposal was written to fix.&lt;/p&gt;
&lt;h3&gt;
  
  
  1.3a The actual GC algorithm: tricolor mark-sweep with a hybrid write barrier
&lt;/h3&gt;

&lt;p&gt;Everything above (1.3) covers &lt;em&gt;when&lt;/em&gt; goroutines get stopped for GC. This&lt;br&gt;
subsection covers what the GC is actually doing — read directly from&lt;br&gt;
&lt;a href="https://github.com/golang/go/blob/master/src/runtime/mgc.go" rel="noopener noreferrer"&gt;&lt;code&gt;src/runtime/mgc.go&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
(top-of-file doc comment) and&lt;br&gt;
&lt;a href="https://github.com/golang/go/blob/master/src/runtime/mbarrier.go" rel="noopener noreferrer"&gt;&lt;code&gt;src/runtime/mbarrier.go&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
(top-of-file doc comment), not summarized from memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What &lt;code&gt;mgc.go&lt;/code&gt; states directly, line for line:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// The GC runs concurrently with mutator threads, is type accurate (aka precise), allows multiple&lt;/span&gt;
&lt;span class="c"&gt;// GC threads to run in parallel. It is a concurrent mark and sweep that uses a write barrier. It is&lt;/span&gt;
&lt;span class="c"&gt;// non-generational and non-compacting.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"concurrent mark and sweep" — the mark phase (finding live objects) and
sweep phase (reclaiming dead ones) both run alongside your program's own
goroutines, not just alongside each other.&lt;/li&gt;
&lt;li&gt;"non-generational" — Go's GC does &lt;em&gt;not&lt;/em&gt; separate young/old objects into
different heaps swept at different rates, unlike HotSpot's G1 or the
young/old generation split most JVM/V8 GCs use. Every GC cycle scans the
whole live set. This is a stated design tradeoff, not an oversight; the
source doesn't give the rationale for the choice.&lt;/li&gt;
&lt;li&gt;"non-compacting" — dead objects are freed in place; Go doesn't move
live objects around to defragment the heap the way a compacting
collector does. This is also why Go's stack-map / pointer-tracking
machinery (Part 1.3) doesn't need to handle objects moving mid-flight —
one less category of complexity than a moving collector like ZGC.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The three-color abstraction, sourced from the mark-phase walkthrough&lt;br&gt;
(&lt;code&gt;mgc.go&lt;/code&gt;, steps 2b–2d) plus the explicit invariant comment further down&lt;br&gt;
the same file:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// At this point all Ps have enabled the write&lt;/span&gt;
&lt;span class="c"&gt;// barrier, thus maintaining the no white to&lt;/span&gt;
&lt;span class="c"&gt;// black invariant.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Objects are conceptually white (not yet visited — presumed garbage),&lt;br&gt;
grey (visited, but its own pointers not yet scanned), or black (visited&lt;br&gt;
&lt;em&gt;and&lt;/em&gt; all its pointers scanned). The mark phase's job is to walk grey&lt;br&gt;
objects until none remain — at that point everything reachable is black,&lt;br&gt;
everything still white is garbage. The &lt;strong&gt;invariant being protected&lt;/strong&gt; —&lt;br&gt;
"no white to black" — is: a black object (already fully scanned) must&lt;br&gt;
never gain a pointer directly to a white object without that white object&lt;br&gt;
getting shaded first. If that invariant were violated, the GC could finish&lt;br&gt;
marking, still see the object as white, and free something the mutator&lt;br&gt;
(your running program) still holds a live reference to. That's the&lt;br&gt;
correctness property the write barrier mechanism below exists to uphold.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The write barrier itself, from &lt;code&gt;mbarrier.go&lt;/code&gt;'s pseudocode:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Go uses a hybrid barrier that combines a Yuasa-style deletion&lt;/span&gt;
&lt;span class="c"&gt;// barrier—which shades the object whose reference is being&lt;/span&gt;
&lt;span class="c"&gt;// overwritten—with Dijkstra insertion barrier—which shades the object&lt;/span&gt;
&lt;span class="c"&gt;// whose reference is being written.&lt;/span&gt;
&lt;span class="c"&gt;//&lt;/span&gt;
&lt;span class="c"&gt;//     writePointer(slot, ptr):&lt;/span&gt;
&lt;span class="c"&gt;//         shade(*slot)&lt;/span&gt;
&lt;span class="c"&gt;//         if current stack is grey:&lt;/span&gt;
&lt;span class="c"&gt;//             shade(ptr)&lt;/span&gt;
&lt;span class="c"&gt;//         *slot = ptr&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the reasoning for why &lt;em&gt;both&lt;/em&gt; halves are needed, stated as three&lt;br&gt;
numbered cases in the source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// 1. shade(*slot) prevents a mutator from hiding an object by moving&lt;/span&gt;
&lt;span class="c"&gt;// the sole pointer to it from the heap to its stack.&lt;/span&gt;
&lt;span class="c"&gt;// 2. shade(ptr) prevents a mutator from hiding an object by moving&lt;/span&gt;
&lt;span class="c"&gt;// the sole pointer to it from its stack into a black object in the heap.&lt;/span&gt;
&lt;span class="c"&gt;// 3. Once a goroutine's stack is black, the shade(ptr) becomes&lt;/span&gt;
&lt;span class="c"&gt;// unnecessary.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the pseudocode from the file's doc comment. The actual code that&lt;br&gt;
runs it — &lt;code&gt;typedmemmove&lt;/code&gt;, the function every pointer-containing struct&lt;br&gt;
copy in Go goes through — shows the same idea gated by a real runtime&lt;br&gt;
check, not just documented intent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;//go:linkname typedmemmove&lt;/span&gt;
&lt;span class="c"&gt;//go:nosplit&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;typedmemmove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;typ&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;abi&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="n"&gt;unsafe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;writeBarrier&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;typ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pointers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// This always copies a full value of type typ so it's safe&lt;/span&gt;
        &lt;span class="c"&gt;// to pass typ along as an optimization. See the comment on&lt;/span&gt;
        &lt;span class="c"&gt;// bulkBarrierPreWrite.&lt;/span&gt;
        &lt;span class="n"&gt;bulkBarrierPreWrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;typ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PtrBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;typ&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;// There's a race here: if some other goroutine can write to&lt;/span&gt;
    &lt;span class="c"&gt;// src, it may change some pointer in src after we've&lt;/span&gt;
    &lt;span class="c"&gt;// performed the write barrier but before we perform the&lt;/span&gt;
    &lt;span class="c"&gt;// memory copy. This safe because the write performed by that&lt;/span&gt;
    &lt;span class="c"&gt;// other goroutine must also be accompanied by a write&lt;/span&gt;
    &lt;span class="c"&gt;// barrier, so at worst we've unnecessarily greyed the old&lt;/span&gt;
    &lt;span class="c"&gt;// pointer that was in src.&lt;/span&gt;
    &lt;span class="n"&gt;memmove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;typ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Size_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things this shows that the pseudocode alone doesn't: the check is&lt;br&gt;
&lt;code&gt;writeBarrier.enabled &amp;amp;&amp;amp; typ.Pointers()&lt;/code&gt; — the barrier only runs at all if&lt;br&gt;
the type being copied actually contains pointers, so copying a struct of&lt;br&gt;
plain integers pays nothing. And the barrier call (&lt;code&gt;bulkBarrierPreWrite&lt;/code&gt;,&lt;br&gt;
which does the &lt;code&gt;shade(*slot)&lt;/code&gt;/&lt;code&gt;shade(ptr)&lt;/code&gt; work from the pseudocode above)&lt;br&gt;
happens strictly &lt;em&gt;before&lt;/em&gt; the &lt;code&gt;memmove&lt;/code&gt; that performs the real copy — the&lt;br&gt;
comment directly under it explains why a race on &lt;code&gt;src&lt;/code&gt; between the&lt;br&gt;
barrier and the copy is still safe: the other goroutine's own write to&lt;br&gt;
&lt;code&gt;src&lt;/code&gt; would have gone through its own barrier already, so at worst an&lt;br&gt;
object gets greyed unnecessarily, never missed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; a write barrier is code the compiler inserts around&lt;br&gt;
&lt;em&gt;every&lt;/em&gt; pointer write during the concurrent mark phase — not a hardware&lt;br&gt;
mechanism like the JVM's poll page (Part 3.5.2), but a software&lt;br&gt;
instrumentation cost paid only while &lt;code&gt;gcphase == _GCmark&lt;/code&gt; (confirmed by&lt;br&gt;
&lt;code&gt;mgc.go&lt;/code&gt;'s phase-transition code, which flips &lt;code&gt;gcBlackenEnabled&lt;/code&gt;/enables&lt;br&gt;
the barrier at mark start and disables it at sweep start). The two halves&lt;br&gt;
guard the two directions an object could otherwise get "lost": deleting&lt;br&gt;
the only pointer to it from an already-scanned (black) part of the heap&lt;br&gt;
(case 1, Yuasa-style), or the mutator itself moving a pointer from its own&lt;br&gt;
unscanned stack into a black object before the GC ever sees it (case 2,&lt;br&gt;
Dijkstra-style). Case 3 is the source explaining its own optimization —&lt;br&gt;
once a goroutine's &lt;em&gt;whole stack&lt;/em&gt; has been scanned (turned black), the&lt;br&gt;
insertion half becomes redundant, because nothing on that stack can be&lt;br&gt;
hiding an unshaded pointer anymore.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open question:&lt;/strong&gt; the mark-work-stealing/distributed-termination&lt;br&gt;
algorithm in &lt;code&gt;gcMarkDone&lt;/code&gt; (mentioned but not shown above), and the&lt;br&gt;
concurrent sweep's per-span locking scheme — not covered by these two&lt;br&gt;
files.&lt;/p&gt;
&lt;h3&gt;
  
  
  1.4 Checkpoint
&lt;/h3&gt;

&lt;p&gt;Trace the actual failure this fixed: before 1.14, &lt;code&gt;for {}&lt;/code&gt; with no&lt;br&gt;
function calls inside it. What specifically blocked, and for how long?&lt;br&gt;
(Answer: any &lt;code&gt;stopTheWorldWithSema&lt;/code&gt; call in &lt;code&gt;mgc.go&lt;/code&gt; — i.e. every GC mark&lt;br&gt;
or sweep termination phase — blocked indefinitely, because the goroutine&lt;br&gt;
never reached the stack-bound check that synchronous preemption relies on.&lt;br&gt;
This wasn't a latency inconvenience; it was an unbounded stall.)&lt;/p&gt;


&lt;h2&gt;
  
  
  Part 2 — Kotlin
&lt;/h2&gt;

&lt;p&gt;Sources read directly for this section: &lt;code&gt;kotlin.coroutines.Continuation&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;kotlin.coroutines.intrinsics&lt;/code&gt; (JetBrains/kotlin, stdlib — this part is&lt;br&gt;
&lt;strong&gt;language-level&lt;/strong&gt;), and &lt;code&gt;kotlinx.coroutines.CoroutineDispatcher&lt;/code&gt; /&lt;br&gt;
&lt;code&gt;kotlinx.coroutines.scheduling.CoroutineScheduler&lt;/code&gt; (Kotlin/kotlinx.coroutines&lt;br&gt;
— this part is a &lt;strong&gt;library&lt;/strong&gt;, not the compiler or the language spec).&lt;/p&gt;

&lt;p&gt;This split is the correction from the previous draft: &lt;code&gt;suspend&lt;/code&gt; and the&lt;br&gt;
CPS transform are compiler + stdlib. &lt;code&gt;Dispatchers&lt;/code&gt;, &lt;code&gt;Job&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;CoroutineScope&lt;/code&gt;, structured concurrency, and the actual thread-pool&lt;br&gt;
scheduler are &lt;strong&gt;kotlinx.coroutines&lt;/strong&gt;, a library published by JetBrains but&lt;br&gt;
not part of Kotlin itself — you can write &lt;code&gt;suspend&lt;/code&gt; functions with zero&lt;br&gt;
dependency on kotlinx.coroutines; it only becomes necessary once you want&lt;br&gt;
dispatchers, structured cancellation, &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;-style builders, or&lt;br&gt;
&lt;code&gt;Flow&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.1 Compiler + stdlib layer: what &lt;code&gt;suspend&lt;/code&gt; actually is
&lt;/h3&gt;

&lt;p&gt;Kotlin runs primarily on the JVM. It cannot change the JVM's thread model —&lt;br&gt;
JVM threads are OS threads. The lever available is using fewer of them,&lt;br&gt;
which means: don't let a suspended computation hold a thread's stack.&lt;/p&gt;

&lt;p&gt;The mechanism is CPS (Continuation-Passing Style), and the actual&lt;br&gt;
interface it compiles against is &lt;code&gt;kotlin.coroutines.Continuation&lt;/code&gt;, defined&lt;br&gt;
in stdlib:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Continuation&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CoroutineContext&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;resumeWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire primitive. A &lt;code&gt;suspend&lt;/code&gt; function, at the ABI level, is a&lt;br&gt;
regular JVM method that takes an extra trailing &lt;code&gt;Continuation&lt;/code&gt; parameter&lt;br&gt;
and returns &lt;code&gt;Any?&lt;/code&gt; — either the real result, or the sentinel&lt;br&gt;
&lt;code&gt;COROUTINE_SUSPENDED&lt;/code&gt; (defined in &lt;code&gt;kotlin.coroutines.intrinsics&lt;/code&gt;,&lt;br&gt;
stdlib) if it suspended. &lt;code&gt;suspendCoroutineUninterceptedOrReturn&lt;/code&gt; is the&lt;br&gt;
stdlib intrinsic that exposes the raw continuation to library authors&lt;br&gt;
building suspension primitives on top.&lt;/p&gt;

&lt;p&gt;The compiler transforms a &lt;code&gt;suspend&lt;/code&gt; function's body into a state machine&lt;br&gt;
object implementing &lt;code&gt;Continuation&lt;/code&gt;, with a &lt;code&gt;label&lt;/code&gt; field tracking progress&lt;br&gt;
and local variables lifted into fields, roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;fetchAndProcess&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FetchAndProcessContinuation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Continuation&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Continuation&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;label&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;resumeWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrThrow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOrThrow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resumeWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function returns (freeing the thread) instead of blocking; the&lt;br&gt;
continuation object on the heap carries the state needed to resume. All of&lt;br&gt;
this — &lt;code&gt;Continuation&lt;/code&gt;, the CPS transform, &lt;code&gt;suspend&lt;/code&gt; as a keyword — is&lt;br&gt;
&lt;strong&gt;part of the Kotlin language and compiler&lt;/strong&gt;, independent of any&lt;br&gt;
concurrency library. This is why &lt;code&gt;suspend&lt;/code&gt; functions work fine in, say, a&lt;br&gt;
minimal environment with no kotlinx.coroutines dependency at all, as long&lt;br&gt;
as something implements &lt;code&gt;Continuation&lt;/code&gt; to drive them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tracing it through &lt;code&gt;launch&lt;/code&gt;, which resolves the open question from&lt;br&gt;
2.4a about where &lt;code&gt;DispatchedContinuation&lt;/code&gt; gets constructed:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;runBlocking&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// suspend point&lt;/span&gt;
        &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"B"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;launch&lt;/code&gt; (a kotlinx.coroutines builder, library-layer) takes the lambda,&lt;br&gt;
which the compiler has already turned into a &lt;code&gt;Continuation&lt;/code&gt;-implementing&lt;br&gt;
state machine roughly like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LambdaContinuation&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Continuation&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;label&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;resumeWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// returns here&lt;/span&gt;
            &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"B"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;launch&lt;/code&gt; wraps that state machine in a &lt;code&gt;DispatchedContinuation&lt;/code&gt; (Part 2.4a)&lt;br&gt;
before ever calling it. When execution reaches &lt;code&gt;delay(1000)&lt;/code&gt;, the function&lt;br&gt;
returns immediately — the thread is free, not blocked — and a timer is&lt;br&gt;
armed. When the timer fires 1000ms later, something calls &lt;code&gt;resumeWith&lt;/code&gt; on&lt;br&gt;
the &lt;em&gt;same&lt;/em&gt; &lt;code&gt;DispatchedContinuation&lt;/code&gt; object; its &lt;code&gt;resumeWith&lt;/code&gt; override (Part&lt;br&gt;
2.4a) checks &lt;code&gt;dispatcher.safeIsDispatchNeeded&lt;/code&gt; and routes the resume&lt;br&gt;
through the dispatcher's thread pool if needed, which then calls the&lt;br&gt;
wrapped continuation's &lt;code&gt;resumeWith&lt;/code&gt;, landing back in &lt;code&gt;label = 1&lt;/code&gt; and&lt;br&gt;
printing "B" — possibly on a completely different thread than the one&lt;br&gt;
that printed "A".&lt;/p&gt;

&lt;p&gt;The cooperative part: the compiler only inserted a resumable point at&lt;br&gt;
&lt;code&gt;delay&lt;/code&gt;, because &lt;code&gt;delay&lt;/code&gt; is a &lt;code&gt;suspend&lt;/code&gt; call. A &lt;code&gt;launch { while (true) {} }&lt;/code&gt;&lt;br&gt;
with no suspend calls inside the loop has no state machine transition&lt;br&gt;
anywhere in the loop body — nothing for any dispatcher to interrupt into,&lt;br&gt;
unlike Go's &lt;code&gt;for {}&lt;/code&gt; example above, which the runtime can still signal&lt;br&gt;
into from outside regardless of what the loop body does.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.2 Why this makes suspension cooperative
&lt;/h3&gt;

&lt;p&gt;The compiler must know, at compile time, every point where execution might&lt;br&gt;
suspend — that's the entire reason &lt;code&gt;suspend&lt;/code&gt; is a keyword rather than a&lt;br&gt;
runtime annotation. If a coroutine body never calls a suspend function&lt;br&gt;
inside a loop, the compiler never inserts a state-machine transition&lt;br&gt;
there, so there is no return/yield point to force. There's no external&lt;br&gt;
mechanism analogous to Go's &lt;code&gt;SIGURG&lt;/code&gt; that could interrupt it, because&lt;br&gt;
nothing external to the continuation object was ever created to interrupt&lt;br&gt;
&lt;em&gt;into&lt;/em&gt; — the compiler-generated state machine, not raw thread register&lt;br&gt;
state, is the unit of suspension, and it only exists at points the source&lt;br&gt;
code actually marked as suspendable.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.3 Library layer: kotlinx.coroutines and what it adds
&lt;/h3&gt;

&lt;p&gt;Continuation alone gives you suspension; it gives you nothing about &lt;em&gt;where&lt;/em&gt;&lt;br&gt;
code resumes, cancellation, or structured lifetimes. That's what&lt;br&gt;
kotlinx.coroutines is for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;CoroutineDispatcher&lt;/code&gt;&lt;/strong&gt; implements &lt;code&gt;ContinuationInterceptor&lt;/code&gt; — it's
what decides &lt;em&gt;which thread&lt;/em&gt; a continuation resumes on.
&lt;code&gt;Dispatchers.Default&lt;/code&gt; and &lt;code&gt;Dispatchers.IO&lt;/code&gt; are the common ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;CoroutineScheduler&lt;/code&gt;&lt;/strong&gt; (&lt;code&gt;kotlinx-coroutines-core/jvm/.../scheduling/&lt;/code&gt;)
is the actual thread pool behind &lt;code&gt;Dispatchers.Default&lt;/code&gt;/&lt;code&gt;IO&lt;/code&gt;. Its own doc
comment is explicit about the design lineage:
&amp;gt; "The original idea with a single-slot LIFO buffer comes from Golang
&amp;gt; runtime scheduler by D. Vyukov. It was proven to be 'fair enough',
&amp;gt; performant and generally well accepted and initially was a significant
&amp;gt; inspiration source for the coroutine scheduler."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Concretely: per-worker local run queues, work-stealing when a worker's&lt;br&gt;
  queue empties, and a global queue for tasks submitted from outside the&lt;br&gt;
  pool — the same shape as Go's per-P local run queues plus work-stealing&lt;br&gt;
  (&lt;code&gt;findRunnable&lt;/code&gt; in &lt;code&gt;proc.go&lt;/code&gt;). This is convergent evolution &lt;em&gt;by direct&lt;br&gt;
  admission&lt;/em&gt;, not independent design — Kotlin's authors read Go's&lt;br&gt;
  scheduler and reused the shape for a completely different substrate (JVM&lt;br&gt;
  threads instead of goroutines).&lt;/p&gt;

&lt;p&gt;The actual &lt;code&gt;dispatch&lt;/code&gt; function, not just the doc comment describing it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Runnable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;taskContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TaskContext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NonBlockingContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fair&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;trackTask&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// this is needed for virtual time support&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;task&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;taskContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isBlockingTask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isBlocking&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;stateSnapshot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isBlockingTask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;incrementBlockingTasks&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="c1"&gt;// try to submit the task to the local queue and act depending on the result&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;currentWorker&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;currentWorker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;notAdded&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;currentWorker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submitToLocalQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fair&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;notAdded&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="nf"&gt;addToGlobalQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;notAdded&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;RejectedExecutionException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"$schedulerName was terminated"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isBlockingTask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;signalBlockingWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stateSnapshot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;signalCpuWork&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the local-queue-first, global-queue-as-overflow policy the doc&lt;br&gt;
  comment describes, made concrete: &lt;code&gt;submitToLocalQueue&lt;/code&gt; tries the calling&lt;br&gt;
  worker's own queue first (the cheap, no-contention path); only if that&lt;br&gt;
  fails (&lt;code&gt;notAdded != null&lt;/code&gt; — the local queue is full) does the task fall&lt;br&gt;
  back to &lt;code&gt;addToGlobalQueue&lt;/code&gt;, the shared, contended queue every idle&lt;br&gt;
  worker also checks. The blocking-vs-CPU distinction&lt;br&gt;
  (&lt;code&gt;signalBlockingWork&lt;/code&gt;/&lt;code&gt;signalCpuWork&lt;/code&gt;) is Kotlin-specific — Go's GMP&lt;br&gt;
  model has no equivalent split, since a goroutine that blocks in a&lt;br&gt;
  syscall just releases its P (Part 1.2) rather than being classified&lt;br&gt;
  ahead of time by the scheduler.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Job&lt;/code&gt;, &lt;code&gt;CoroutineScope&lt;/code&gt;, structured concurrency&lt;/strong&gt; — the parent/child
cancellation tree that ensures a scope can't complete while children are
still running, and that cancelling a parent cancels all children. This
is the closest thing Kotlin has to fork-join semantics, but it's opt-in
library behavior layered on top of continuations, not a language feature.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2.4 GC consequence
&lt;/h3&gt;

&lt;p&gt;Kotlin/JVM's GC is whatever the underlying JVM GC is (G1, ZGC, etc.).&lt;br&gt;
Coroutines don't change the GC algorithm — they change what's running on a&lt;br&gt;
given OS thread at a given moment, and they add continuation objects to&lt;br&gt;
the heap (one allocation per suspend point per invocation, though the JVM&lt;br&gt;
JIT can sometimes eliminate short-lived ones). Suspension does not create&lt;br&gt;
a GC safe-point problem analogous to Go's, because JVM safe-points are a&lt;br&gt;
separate, JIT-inserted polling mechanism entirely orthogonal to coroutine&lt;br&gt;
suspension — see Part 4 for how HotSpot's safepoint polling actually works,&lt;br&gt;
which is the real GC-relevant mechanism on this stack, not the coroutine&lt;br&gt;
machinery itself.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.4a Continuation and DispatchedContinuation, from source
&lt;/h3&gt;

&lt;p&gt;Read directly:&lt;br&gt;
&lt;a href="https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/kotlin/coroutines/Continuation.kt" rel="noopener noreferrer"&gt;&lt;code&gt;kotlin/libraries/stdlib/src/kotlin/coroutines/Continuation.kt&lt;/code&gt;&lt;/a&gt;,&lt;br&gt;
&lt;a href="https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/kotlin/coroutines/intrinsics/Intrinsics.kt" rel="noopener noreferrer"&gt;&lt;code&gt;kotlin/libraries/stdlib/src/kotlin/coroutines/intrinsics/Intrinsics.kt&lt;/code&gt;&lt;/a&gt;,&lt;br&gt;
&lt;a href="https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt" rel="noopener noreferrer"&gt;&lt;code&gt;kotlinx.coroutines/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Continuation.kt&lt;/code&gt;, the entire interface, quoted in full because it's short&lt;br&gt;
enough that summarizing it would lose information:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Continuation&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CoroutineContext&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;resumeWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; the stdlib's contract for "a suspended computation" is&lt;br&gt;
exactly two things — a context bag, and one method to call when a result&lt;br&gt;
is ready. Nothing here mentions threads, dispatchers, or scheduling:&lt;br&gt;
thread placement is not a compiler-level concern, since the interface the&lt;br&gt;
compiler generates against has no slot for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Intrinsics.kt&lt;/code&gt;, the actual suspension primitive:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;inline&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;suspendCoroutineUninterceptedOrReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;crossinline&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Continuation&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;contract&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;callsInPlace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;InvocationKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EXACTLY_ONCE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;NotImplementedError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Implementation of suspendCoroutineUninterceptedOrReturn is intrinsic"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; the body is a &lt;code&gt;throw&lt;/code&gt; — this function has no real&lt;br&gt;
implementation in Kotlin source at all. The comment above it in the file&lt;br&gt;
(not reproduced above) states the implementation is intrinsic: the&lt;br&gt;
compiler recognizes this specific function by name and replaces the call&lt;br&gt;
with generated bytecode directly. The CPS transform is not a library&lt;br&gt;
trick built on top of ordinary language features — the compiler has&lt;br&gt;
special-cased knowledge of this function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open question:&lt;/strong&gt; the compiler's codegen for the intrinsic itself is not&lt;br&gt;
visible from this file — only the signature and doc-comment stating that&lt;br&gt;
special-casing happens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;DispatchedContinuation.kt&lt;/code&gt;, confirming the wrapping claim from&lt;br&gt;
checkpoint 2.5:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DispatchedContinuation&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@JvmField&lt;/span&gt; &lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;dispatcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CoroutineDispatcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@JvmField&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;continuation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Continuation&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DispatchedTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nc"&gt;MODE_UNINITIALIZED&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;CoroutineStackFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Continuation&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;continuation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; &lt;code&gt;DispatchedContinuation&lt;/code&gt; holds both a &lt;code&gt;dispatcher&lt;/code&gt; field and&lt;br&gt;
the original compiler-generated &lt;code&gt;continuation&lt;/code&gt; as a delegate&lt;br&gt;
(&lt;code&gt;Continuation&amp;lt;T&amp;gt; by continuation&lt;/code&gt;). It wraps the compiler's continuation&lt;br&gt;
rather than replacing it — the compiler produces the plain &lt;code&gt;Continuation&lt;/code&gt;;&lt;br&gt;
kotlinx.coroutines wraps it in a dispatcher-aware class before anything&lt;br&gt;
calls &lt;code&gt;resumeWith&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The actual dispatch decision — where the thread-hop the field declarations&lt;br&gt;
above only imply actually happens — is &lt;code&gt;DispatchedContinuation&lt;/code&gt;'s own&lt;br&gt;
&lt;code&gt;resumeWith&lt;/code&gt; override:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;resumeWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dispatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeIsDispatchNeeded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;
        &lt;span class="n"&gt;resumeMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MODE_ATOMIC&lt;/span&gt;
        &lt;span class="n"&gt;dispatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeDispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;executeUnconfined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MODE_ATOMIC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;withCoroutineContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;countOrElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;continuation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resumeWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time the wrapped operation completes, this is what actually runs&lt;br&gt;
(not the plain &lt;code&gt;Continuation.resumeWith&lt;/code&gt; interface method from stdlib,&lt;br&gt;
Part 2.4a). It asks the dispatcher &lt;code&gt;safeIsDispatchNeeded(context)&lt;/code&gt; — if&lt;br&gt;
the current thread is already the right one (e.g. an unconfined&lt;br&gt;
dispatcher, or resuming on the same thread that's already correct), it&lt;br&gt;
calls straight through to the wrapped &lt;code&gt;continuation.resumeWith(result)&lt;/code&gt;&lt;br&gt;
in place, no thread hop. Otherwise it stashes the result in &lt;code&gt;_state&lt;/code&gt; and&lt;br&gt;
calls &lt;code&gt;dispatcher.safeDispatch(context, this)&lt;/code&gt;, which schedules this same&lt;br&gt;
object onto the dispatcher's thread pool (Part 2.3's &lt;code&gt;CoroutineScheduler&lt;/code&gt;)&lt;br&gt;
to be resumed there. This is the concrete mechanism behind "the dispatcher&lt;br&gt;
decides what thread to resume on" (checkpoint 2.5's answer) — a runtime&lt;br&gt;
branch inside &lt;code&gt;resumeWith&lt;/code&gt;, not something the compiler's state machine&lt;br&gt;
knows anything about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open question:&lt;/strong&gt; the literal source line inside kotlinx.coroutines'&lt;br&gt;
&lt;code&gt;launch&lt;/code&gt;/&lt;code&gt;async&lt;/code&gt; implementation where a &lt;code&gt;DispatchedContinuation&lt;/code&gt; gets&lt;br&gt;
constructed — Part 2.1's &lt;code&gt;launch&lt;/code&gt;/&lt;code&gt;delay&lt;/code&gt; walkthrough traces the mechanism&lt;br&gt;
conceptually (what gets wrapped, when it resumes, which thread), but the&lt;br&gt;
actual builder source file itself hasn't been read to confirm the&lt;br&gt;
construction call site line-for-line.&lt;/p&gt;
&lt;h3&gt;
  
  
  2.5 Checkpoint
&lt;/h3&gt;

&lt;p&gt;Two continuations exist for a suspending coroutine call: the compiler&lt;br&gt;
generates one automatically. Where does the &lt;em&gt;dispatcher&lt;/em&gt; decide what&lt;br&gt;
thread to resume on — inside &lt;code&gt;Continuation.resumeWith&lt;/code&gt;, or somewhere&lt;br&gt;
kotlinx.coroutines wraps around it? (Answer: kotlinx.coroutines wraps the&lt;br&gt;
compiler-generated continuation with &lt;code&gt;ContinuationInterceptor.interceptContinuation&lt;/code&gt;,&lt;br&gt;
producing a &lt;code&gt;DispatchedContinuation&lt;/code&gt; that routes the actual &lt;code&gt;resumeWith&lt;/code&gt;&lt;br&gt;
call through the dispatcher's thread pool. The compiler's state machine&lt;br&gt;
has no thread-affinity logic of its own — that's entirely library-layer.)&lt;/p&gt;


&lt;h2&gt;
  
  
  Part 3 — BEAM
&lt;/h2&gt;

&lt;p&gt;Sources read directly for this section:&lt;br&gt;
&lt;a href="https://github.com/erlang/otp/blob/master/erts/emulator/beam/erl_process.c" rel="noopener noreferrer"&gt;&lt;code&gt;erts/emulator/beam/erl_process.c&lt;/code&gt;&lt;/a&gt;,&lt;br&gt;
&lt;a href="https://github.com/erlang/otp/blob/master/erts/emulator/beam/erl_vm.h" rel="noopener noreferrer"&gt;&lt;code&gt;erts/emulator/beam/erl_vm.h&lt;/code&gt;&lt;/a&gt;,&lt;br&gt;
&lt;a href="https://github.com/erlang/otp/blob/master/erts/emulator/beam/emu/beam_emu.c" rel="noopener noreferrer"&gt;&lt;code&gt;erts/emulator/beam/emu/beam_emu.c&lt;/code&gt;&lt;/a&gt;,&lt;br&gt;
&lt;a href="https://github.com/erlang/otp/blob/master/erts/emulator/beam/erl_gc.c" rel="noopener noreferrer"&gt;&lt;code&gt;erts/emulator/beam/erl_gc.c&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
— erlang/otp, master branch.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.1 Constraint
&lt;/h3&gt;

&lt;p&gt;Erlang's original design question (Joe Armstrong) was not about&lt;br&gt;
performance — it was: how do you build a system that can run essentially&lt;br&gt;
forever without going down? The answer: faults must be contained, which&lt;br&gt;
requires isolation, which requires sharing nothing.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.2 Design: per-process heap, no shared memory
&lt;/h3&gt;

&lt;p&gt;BEAM processes (VM-managed, not OS processes) share no memory. Each has&lt;br&gt;
its own heap and its own GC. &lt;code&gt;beam_emu.c&lt;/code&gt;'s &lt;code&gt;process_main&lt;/code&gt; makes this&lt;br&gt;
concrete — the interpreter loop's registers are literally per-process&lt;br&gt;
state, not per-OS-thread:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Pointer to X registers: x(0)..x(N). */&lt;/span&gt;
&lt;span class="k"&gt;register&lt;/span&gt; &lt;span class="n"&gt;Eterm&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;reg&lt;/span&gt; &lt;span class="n"&gt;REG_xregs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/* Top of heap (next free location); grows upwards. */&lt;/span&gt;
&lt;span class="k"&gt;register&lt;/span&gt; &lt;span class="n"&gt;Eterm&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;HTOP&lt;/span&gt; &lt;span class="n"&gt;REG_htop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/* Stack pointer. Grows downwards; points
 * to last item pushed (normally a saved
 * continuation pointer). */&lt;/span&gt;
&lt;span class="k"&gt;register&lt;/span&gt; &lt;span class="n"&gt;Eterm&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;E&lt;/span&gt; &lt;span class="n"&gt;REG_stop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each process being swapped onto a scheduler thread brings its own heap&lt;br&gt;
pointer (&lt;code&gt;HTOP&lt;/code&gt;), its own stack pointer (&lt;code&gt;E&lt;/code&gt;), and its own register file&lt;br&gt;
(&lt;code&gt;reg&lt;/code&gt;) — nothing here is shared across processes the way a JVM heap or Go&lt;br&gt;
heap is shared across threads/goroutines. Sending a message to another&lt;br&gt;
process copies the data into the target's heap (large binaries are the&lt;br&gt;
exception — reference-counted in a shared binary heap to avoid the copy).&lt;/p&gt;

&lt;p&gt;Because no process can see another's stack or heap, there's no shared&lt;br&gt;
safe-point problem: the scheduler never needs to reason about pointer&lt;br&gt;
validity in &lt;em&gt;another&lt;/em&gt; process's memory to preempt one process, which is&lt;br&gt;
exactly the constraint Go's stack maps (Part 1.3) and HotSpot's poll pages&lt;br&gt;
(Part 3.5) both exist to satisfy and BEAM structurally doesn't need to.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.3 Key concept: what a reduction actually is
&lt;/h3&gt;

&lt;p&gt;Before the mechanism, the term itself. "Reduction" is not BEAM-specific&lt;br&gt;
jargon invented for scheduling — it comes from &lt;strong&gt;term rewriting /&lt;br&gt;
reduction semantics&lt;/strong&gt; in programming language theory: evaluating an&lt;br&gt;
expression is a sequence of "reduction steps," each rewriting the term&lt;br&gt;
closer to a final value (the same root sense as "beta reduction" in lambda&lt;br&gt;
calculus — Erlang's interpreter is, structurally, a term-rewriting&lt;br&gt;
machine, and BEAM is literally short for "Bogdan/Björn's Erlang Abstract&lt;br&gt;
Machine," an abstract machine executing that rewriting). A BEAM reduction&lt;br&gt;
is the &lt;em&gt;scheduling&lt;/em&gt; projection of that idea: one accounting unit charged&lt;br&gt;
per unit of interpreter work — approximately one function call, one BIF&lt;br&gt;
(built-in function) call, or one comparable primitive operation — deducted&lt;br&gt;
from a process's budget every time the interpreter dispatches one.&lt;/p&gt;

&lt;p&gt;It is deliberately &lt;strong&gt;not&lt;/strong&gt; a fixed wall-clock unit. It's an abstraction&lt;br&gt;
that lets the scheduler compare "how much work has this process done"&lt;br&gt;
across processes without needing a hardware timer interrupt (contrast Part&lt;br&gt;
0's OS timer-interrupt preemption) — the interpreter itself is the thing&lt;br&gt;
counting, inline, as part of doing the work. That's what makes it cheap:&lt;br&gt;
there's no separate polling instruction (contrast HotSpot's poll page,&lt;br&gt;
Part 3.5.2) or external interrupt (contrast Go's SIGURG, Part 1.3) —&lt;br&gt;
the decrement is folded into instruction dispatch that was happening&lt;br&gt;
anyway.&lt;/p&gt;

&lt;p&gt;Confirming this isn't purely a scheduling-only concept, &lt;code&gt;erl_process.h&lt;/code&gt;&lt;br&gt;
also uses reductions as a genuine unit of measurement, converting elapsed&lt;br&gt;
wall-clock time into an equivalent reduction count (used, among other&lt;br&gt;
things, to charge a process for time spent blocked so the scheduler's&lt;br&gt;
accounting stays consistent):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;ErtsMonotonicTime&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ERTS_MONOTONIC_TO_USEC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sint64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="cm"&gt;/* At least one reduction */&lt;/span&gt;
&lt;span class="cm"&gt;/* Currently two reductions per micro second */&lt;/span&gt;
&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CONTEXT_REDS&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sint64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the process struct itself (&lt;code&gt;erl_process.h&lt;/code&gt;) documents &lt;code&gt;fcalls&lt;/code&gt;&lt;br&gt;
precisely as the live budget:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;Sint32&lt;/span&gt; &lt;span class="n"&gt;fcalls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="cm"&gt;/* Number of reductions left to execute.
                  * Only valid for the current process while it
                  * is executing. */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So: a reduction is BEAM's unit of "work done," inherited in name from&lt;br&gt;
term-rewriting theory, made concrete as roughly one function/BIF call in&lt;br&gt;
the interpreter, and used both to decide &lt;em&gt;when to preempt&lt;/em&gt; (3.3 below) and&lt;br&gt;
to convert real elapsed time into comparable scheduling accounting.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.3a Reduction vs. safepoint
&lt;/h3&gt;

&lt;p&gt;They answer different questions, and that difference is exactly why the&lt;br&gt;
comparison is useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;safepoint&lt;/strong&gt; (Go, Part 1.3; JVM, Part 3.5) answers &lt;em&gt;"where in the
code is it valid to inspect/pause this thread's state?"&lt;/em&gt; — a spatial
question. Most instructions are &lt;em&gt;not&lt;/em&gt; safe-points; the compiler/runtime
has to specially mark or detect the few that are, because in a
shared-heap, register-based execution model most points mid-instruction
don't have a fully-known, GC-inspectable machine state.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;reduction&lt;/strong&gt; answers &lt;em&gt;"has this process done enough work that it
should yield?"&lt;/em&gt; — a budget question, not a location question.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reason reductions don't need a &lt;em&gt;separate&lt;/em&gt; safepoint concept is that in&lt;br&gt;
BEAM's interpreter, &lt;strong&gt;every reduction boundary already is a valid&lt;br&gt;
safepoint&lt;/strong&gt; — process state (heap pointer, stack pointer, registers) is&lt;br&gt;
always fully known and consistent between BEAM instructions, because the&lt;br&gt;
interpreter loop (&lt;code&gt;process_main&lt;/code&gt;, 3.2) never leaves execution in a&lt;br&gt;
partially-updated state across an instruction dispatch. There's no&lt;br&gt;
"mid-instruction, a pointer is briefly stored as a raw integer" hazard&lt;br&gt;
(Go's &lt;code&gt;unsafe.Pointer&lt;/code&gt;→&lt;code&gt;uintptr&lt;/code&gt; case, Part 1.3) to guard against, because&lt;br&gt;
there's no equivalent unsafe low-level escape hatch in normal BEAM&lt;br&gt;
bytecode execution the way there is in compiled machine code.&lt;/p&gt;

&lt;p&gt;So the precise relationship is: &lt;strong&gt;in Go and the JVM, safe-points are a&lt;br&gt;
sparse subset of execution points, specially constructed at compile time&lt;br&gt;
because most points aren't safe. In BEAM, every point between instructions&lt;br&gt;
is already safe, so the only remaining question is scheduling policy — how&lt;br&gt;
often to actually act on that safety — and that's exactly what the&lt;br&gt;
reduction counter answers.&lt;/strong&gt; Reduction counting is the "when to act"&lt;br&gt;
policy layered on top of an execution model where "where is it safe to&lt;br&gt;
act" was never a hard problem to begin with. This is a direct consequence&lt;br&gt;
of Part 3.2's isolation constraint: shared, GC-managed memory is what&lt;br&gt;
makes "where is it safe" hard in the first place, and BEAM opted out of&lt;br&gt;
that at the memory-model level, not at the scheduling level.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.3.1 Preemption: reduction counting, with the actual constant
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;erl_vm.h&lt;/code&gt; defines the budget directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define CONTEXT_REDS 4000            &lt;/span&gt;&lt;span class="cm"&gt;/* Swap process out after this number */&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Note: this is the current source value. Older Erlang documentation and&lt;br&gt;
blog posts commonly cite ~2000 reductions — that number is stale; always&lt;br&gt;
check the constant in the version you're targeting rather than trusting a&lt;br&gt;
remembered figure, including this document a year from now.)&lt;/p&gt;

&lt;p&gt;The decrement mechanism is the interpreter's own reduction counter,&lt;br&gt;
&lt;code&gt;FCALLS&lt;/code&gt;, documented directly at its declaration in &lt;code&gt;process_main&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* Number of reductions left.  This function
 * returns to the scheduler when FCALLS reaches zero. */&lt;/span&gt;
&lt;span class="k"&gt;register&lt;/span&gt; &lt;span class="n"&gt;Sint&lt;/span&gt; &lt;span class="n"&gt;FCALLS&lt;/span&gt; &lt;span class="n"&gt;REG_fcalls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every BEAM instruction (roughly: one function call, one comparable unit of&lt;br&gt;
work) decrements &lt;code&gt;FCALLS&lt;/code&gt;. When it hits zero, &lt;code&gt;process_main&lt;/code&gt; returns&lt;br&gt;
control to the scheduler, which calls &lt;code&gt;erts_schedule&lt;/code&gt; with the reduction&lt;br&gt;
count actually consumed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;reds_used&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;REDS_IN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c_p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;FCALLS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;c_p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;erts_schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c_p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reds_used&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;erl_process.c&lt;/code&gt; also derives several scheduler tuning constants directly&lt;br&gt;
from &lt;code&gt;CONTEXT_REDS&lt;/code&gt; — e.g. &lt;code&gt;ERTS_RUNQ_CHECK_BALANCE_REDS_PER_SCHED&lt;br&gt;
(2000*CONTEXT_REDS)&lt;/code&gt; for load-balancing cadence between run queues, and&lt;br&gt;
&lt;code&gt;ERTS_PROC_MIN_CONTEXT_SWITCH_REDS_COST (CONTEXT_REDS/10)&lt;/code&gt; — confirming&lt;br&gt;
reductions are the scheduler's universal unit of "how much work happened,"&lt;br&gt;
not just a preemption trigger.&lt;/p&gt;

&lt;p&gt;There is no signal, no stack map, no poll page: preemption here is a&lt;br&gt;
plain integer counter checked as part of every instruction dispatch, cheap&lt;br&gt;
because it's already inline in the interpreter's hot loop, not a bolted-on&lt;br&gt;
external mechanism.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tracing it through a concrete example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erlang"&gt;&lt;code&gt;&lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nb"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nb"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"still runs fine&lt;/span&gt;&lt;span class="si"&gt;~n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;spawn(fun loop/0)&lt;/code&gt; starts a process that never returns — the BEAM&lt;br&gt;
equivalent of Go's &lt;code&gt;for {}&lt;/code&gt;. Unlike Go pre-1.14, this doesn't hang the&lt;br&gt;
system: &lt;code&gt;process_main&lt;/code&gt;'s interpreter loop dispatches &lt;code&gt;loop/0&lt;/code&gt;'s call to&lt;br&gt;
itself as one reduction, decrementing &lt;code&gt;FCALLS&lt;/code&gt; (Part 3.3.1) each time.&lt;br&gt;
When &lt;code&gt;FCALLS&lt;/code&gt; hits zero — after &lt;code&gt;CONTEXT_REDS&lt;/code&gt; (4000, by default)&lt;br&gt;
dispatched calls — &lt;code&gt;process_main&lt;/code&gt; returns control to &lt;code&gt;erts_schedule&lt;/code&gt;&lt;br&gt;
(Part 3.5a), which puts the looping process back on the run queue and&lt;br&gt;
picks the next ready process. The second &lt;code&gt;spawn&lt;/code&gt;, printing "still runs&lt;br&gt;
fine," gets scheduled and runs to completion on the same core without&lt;br&gt;
ever waiting on the infinite loop. No signal was sent to interrupt&lt;br&gt;
&lt;code&gt;loop/0&lt;/code&gt;; it was never running long enough at a stretch to need one — the&lt;br&gt;
counter reaching zero &lt;em&gt;is&lt;/em&gt; the interruption, already built into every&lt;br&gt;
single call the loop makes.&lt;/p&gt;

&lt;p&gt;Because each process owns its heap exclusively, &lt;code&gt;erl_gc.c&lt;/code&gt; runs GC&lt;br&gt;
per-process — a GC pause for one process is invisible to every other&lt;br&gt;
process running concurrently on other scheduler threads. There is no&lt;br&gt;
whole-VM stop-the-world equivalent to Go's &lt;code&gt;stopTheWorldWithSema&lt;/code&gt; (Part&lt;br&gt;
1.3) or HotSpot's global safepoint synchronization (Part 3.5.1): BEAM&lt;br&gt;
never needs "every process frozen at once," because nothing GC-relevant is&lt;br&gt;
ever shared across processes to make that necessary.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.5 Fault model as a consequence, not a philosophy
&lt;/h3&gt;

&lt;p&gt;Because processes are isolated, a crash is just a process exiting — it&lt;br&gt;
cannot corrupt another process's state by construction. This is why "let&lt;br&gt;
it crash" is viable: supervisors restart failed processes and the&lt;br&gt;
remaining system is provably unaffected, because isolation was the&lt;br&gt;
starting constraint everything else (per-process GC, reduction-counted&lt;br&gt;
preemption, copy-on-send messaging) follows from, not an afterthought&lt;br&gt;
layered on top.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.5a &lt;code&gt;erts_schedule&lt;/code&gt;: the descheduling/reschedule handoff
&lt;/h3&gt;

&lt;p&gt;Read directly:&lt;br&gt;
&lt;a href="https://github.com/erlang/otp/blob/master/erts/emulator/beam/erl_process.c" rel="noopener noreferrer"&gt;&lt;code&gt;erts/emulator/beam/erl_process.c&lt;/code&gt;&lt;/a&gt;,&lt;br&gt;
&lt;code&gt;Process *erts_schedule(...)&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;Process&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;erts_schedule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ErtsSchedulerData&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;esdp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Process&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ERTS_USE_MODIFIED_TIMING&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;context_reds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ERTS_MODIFIED_TIMING_CONTEXT_REDS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;context_reds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CONTEXT_REDS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="cm"&gt;/*
     * Clean up after the process being scheduled out.
     */&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="cm"&gt;/* NULL in the very first schedule() call */&lt;/span&gt;
        &lt;span class="n"&gt;is_normal_sched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;esdp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;
        &lt;span class="n"&gt;rq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;erts_get_runq_current&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;esdp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;actual_reds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;erts_runq_lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rq&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; this is the function &lt;code&gt;process_main&lt;/code&gt; (3.3.1) calls once&lt;br&gt;
&lt;code&gt;FCALLS&lt;/code&gt; hits zero — &lt;code&gt;erts_schedule&lt;/code&gt; both cleans up the process being&lt;br&gt;
descheduled and picks and returns the next one, in a single function (the&lt;br&gt;
&lt;code&gt;if (!p)&lt;/code&gt; branch is the bootstrap case with no previous process). The&lt;br&gt;
&lt;code&gt;ERTS_USE_MODIFIED_TIMING()&lt;/code&gt; branch means &lt;code&gt;CONTEXT_REDS&lt;/code&gt; (4000) is not&lt;br&gt;
always the literal number used — there's a "modified timing" mode with a&lt;br&gt;
different constant. The 4000 figure in 3.3.1 is the default, not an&lt;br&gt;
unconditional constant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open question:&lt;/strong&gt; what &lt;code&gt;ERTS_USE_MODIFIED_TIMING()&lt;/code&gt; gates, and the&lt;br&gt;
next-process-selection logic inside &lt;code&gt;erts_schedule&lt;/code&gt; past the cleanup&lt;br&gt;
section shown above (priority levels, run-queue migration) — only the&lt;br&gt;
function's opening is covered here.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.6 Checkpoint
&lt;/h3&gt;

&lt;p&gt;Given &lt;code&gt;CONTEXT_REDS = 4000&lt;/code&gt; and one reduction ≈ one function call: can a&lt;br&gt;
single BEAM process still starve the scheduler the way a Go goroutine&lt;br&gt;
could pre-1.14? Look at what triggers the check. (Answer: no in the same&lt;br&gt;
way — the check is baked into every instruction dispatch inside&lt;br&gt;
&lt;code&gt;process_main&lt;/code&gt;'s interpreter loop, not gated behind reaching a function&lt;br&gt;
call boundary the way Go's old stack-bound check was; there is no BEAM&lt;br&gt;
equivalent of &lt;code&gt;for {}&lt;/code&gt; with literally zero dispatched instructions, since&lt;br&gt;
even an infinite loop's body is instructions being dispatched and&lt;br&gt;
decrementing &lt;code&gt;FCALLS&lt;/code&gt;. The one thing that &lt;em&gt;can&lt;/em&gt; still block a scheduler&lt;br&gt;
thread is a genuinely blocking NIF/native call that doesn't yield control&lt;br&gt;
back to the VM — the moral equivalent of a goroutine in a blocking syscall&lt;br&gt;
without releasing its P.)&lt;/p&gt;


&lt;h2&gt;
  
  
  Part 3.5 — JVM Safepoints (the mechanism Kotlin sits on top of, and Go's real point of contrast)
&lt;/h2&gt;

&lt;p&gt;Sources read directly for this section:&lt;br&gt;
&lt;a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/safepoint.hpp" rel="noopener noreferrer"&gt;&lt;code&gt;src/hotspot/share/runtime/safepoint.hpp&lt;/code&gt;&lt;/a&gt;,&lt;br&gt;
&lt;a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/safepointMechanism.hpp" rel="noopener noreferrer"&gt;&lt;code&gt;src/hotspot/share/runtime/safepointMechanism.hpp&lt;/code&gt;&lt;/a&gt;,&lt;br&gt;
openjdk/jdk, master branch. Also relevant as an applied-engineering account&lt;br&gt;
(not a primary source, but a good "what this looks like in production"&lt;br&gt;
companion): Sunny Srinidhi, &lt;a href="https://medium.com/software-under-the-hood/under-the-hood-java-peak-safepoints-dd45af07d766" rel="noopener noreferrer"&gt;"Under the Hood: Java Peak Safepoints"&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This section was missing entirely from the previous draft even though it's&lt;br&gt;
the actual mechanism Part 2.4 waved at, and it's the correct point of&lt;br&gt;
contrast for Go's approach — not an optional aside.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.5.1 What a safepoint is, precisely, in HotSpot's own words
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;safepoint.hpp&lt;/code&gt;'s header comment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The VMThread uses the SafepointSynchronize::begin/end&lt;/span&gt;
&lt;span class="c1"&gt;// methods to enter/exit a safepoint region. The begin method will roll&lt;/span&gt;
&lt;span class="c1"&gt;// all JavaThreads forward to a safepoint.&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// JavaThreads must use the ThreadSafepointState abstraction (defined in&lt;/span&gt;
&lt;span class="c1"&gt;// thread.hpp) to indicate that that they are at a safepoint.&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// The Mutex/Condition variable and ObjectLocker classes calls the enter/&lt;/span&gt;
&lt;span class="c1"&gt;// exit safepoint methods, when a thread is blocked/restarted. Hence, all&lt;/span&gt;
&lt;span class="c1"&gt;// mutex enter/exit points *must* be at a safepoint.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the three-state enum that drives the whole mechanism:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;SynchronizeState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_not_synchronized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// Threads not synchronized at a safepoint. Keep this value 0.&lt;/span&gt;
    &lt;span class="n"&gt;_synchronizing&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// Synchronizing in progress&lt;/span&gt;
    &lt;span class="n"&gt;_synchronized&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;    &lt;span class="c1"&gt;// All Java threads are running in native, blocked in OS&lt;/span&gt;
                              &lt;span class="c1"&gt;// or stopped at safepoint. VM thread and any NonJavaThread&lt;/span&gt;
                              &lt;span class="c1"&gt;// may be running.&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirms the GC-adjacent framing directly: a safepoint isn't&lt;br&gt;
GC-specific in HotSpot — it's the general mechanism the VM uses any time it&lt;br&gt;
needs every Java thread frozen in a known state (GC, deoptimization,&lt;br&gt;
biased-lock revocation, thread dumps, class redefinition). GC is the most&lt;br&gt;
frequent consumer, not the only one.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.5.2 The poll-page mechanism — how threads notice they should stop
&lt;/h3&gt;

&lt;p&gt;Unlike Go's synchronous safe-points (stack-bound-check poisoning) or async&lt;br&gt;
safe-points (signal delivery), HotSpot's classic mechanism is &lt;strong&gt;memory&lt;br&gt;
polling&lt;/strong&gt;. From &lt;code&gt;safepointMechanism.hpp&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SafepointMechanism&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;AllStatic&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;uintptr_t&lt;/span&gt; &lt;span class="n"&gt;_poll_page_armed_value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;uintptr_t&lt;/span&gt; &lt;span class="n"&gt;_poll_page_disarmed_value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;   &lt;span class="n"&gt;_polling_page&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;ThreadData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;uintptr_t&lt;/span&gt; &lt;span class="n"&gt;_polling_word&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;volatile&lt;/span&gt; &lt;span class="kt"&gt;uintptr_t&lt;/span&gt; &lt;span class="n"&gt;_polling_page&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Call this method to see if this thread should block for a safepoint&lt;/span&gt;
  &lt;span class="c1"&gt;// or process handshake.&lt;/span&gt;
  &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;should_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JavaThread&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;allow_suspend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JIT compiler emits, at method entry and loop back-edges, a memory read&lt;br&gt;
from &lt;code&gt;_polling_page&lt;/code&gt;. Normally this page is mapped read-only-but-armable&lt;br&gt;
and the read is nearly free (a single load the CPU can pipeline away). To&lt;br&gt;
request a safepoint, the VM thread &lt;strong&gt;unmaps or protects the polling page&lt;/strong&gt;&lt;br&gt;
(the "armed" state). The next thread that executes its poll instruction&lt;br&gt;
triggers a trap — a real page fault the OS delivers as a signal/exception&lt;br&gt;
— which HotSpot's fault handler intercepts and redirects into safepoint&lt;br&gt;
processing.&lt;/p&gt;

&lt;p&gt;This is the crucial contrast with Go: HotSpot's mechanism is cooperative&lt;br&gt;
in mechanism (a thread must execute its own poll instruction to notice) but&lt;br&gt;
made cheap and &lt;em&gt;effectively involuntary&lt;/em&gt; by tying the poll to a hardware&lt;br&gt;
trap instead of a branch — a thread genuinely cannot skip past an armed&lt;br&gt;
poll once it executes one, but a thread that never reaches a poll point at&lt;br&gt;
all (analogous to Go's pre-1.14 tight-loop problem) still can't be stopped.&lt;br&gt;
This is exactly the "time to safe-point" (TTSP) latency the linked Medium&lt;br&gt;
piece measures in production — the gap between the VM thread arming the&lt;br&gt;
page and the last straggler thread actually hitting its poll and blocking.&lt;br&gt;
JIT-compiled code has poll points at loop back-edges and method returns/calls&lt;br&gt;
(the compiler decides placement per-method based on loop structure);&lt;br&gt;
interpreter frames check a flag on every bytecode dispatch, which is why&lt;br&gt;
interpreted code reaches safepoints faster but runs slower generally.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.5.2a Exact poll placement, and the safepoint/blocked distinction
&lt;/h3&gt;

&lt;p&gt;Additional sources for this subsection (secondary, but precise and&lt;br&gt;
attributed): Gil Tene's account on the mechanical-sympathy mailing list, as&lt;br&gt;
reproduced at &lt;a href="http://psy-lob-saw.blogspot.com/2015/12/safepoints.html" rel="noopener noreferrer"&gt;Nitsan Wakart, "Safepoints: Meaning, Side Effects and Overheads"&lt;/a&gt;;&lt;br&gt;
also &lt;a href="http://chriskirk.blogspot.com/2013/09/what-is-java-safepoint.html" rel="noopener noreferrer"&gt;chriskirk.blogspot.com, "What is a Java Safepoint?"&lt;/a&gt;&lt;br&gt;
and &lt;a href="http://jpbempel.blogspot.com/2013/03/safety-first-safepoints.html" rel="noopener noreferrer"&gt;jpbempel.blogspot.com, "Safety First: Safepoints"&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;HotSpot doesn't poll unconditionally everywhere — poll placement is a&lt;br&gt;
deliberate tradeoff, because "on top of the cost of the flag check itself,&lt;br&gt;
maintaining a 'known state' adds significant complexity to the&lt;br&gt;
implementation of certain optimizations," so keeping safepoints further&lt;br&gt;
apart widens the scope for optimization. The actual poll locations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Effectively &lt;strong&gt;between any two bytecodes&lt;/strong&gt; while running in the interpreter&lt;/li&gt;
&lt;li&gt;On &lt;strong&gt;non-counted loop back-edges&lt;/strong&gt; in C1/C2-compiled code (a "counted"
loop — bounds known at compile time — can skip the poll, since the JIT
can prove it terminates)&lt;/li&gt;
&lt;li&gt;At &lt;strong&gt;method entry/exit&lt;/strong&gt; in compiled code (entry on Zing, exit on
OpenJDK) — and the compiler removes these polls entirely when a method
gets inlined, since the caller's own poll covers it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the actual poll instructions in &lt;code&gt;-XX:+PrintAssembly&lt;/code&gt; output&lt;br&gt;
by searching for &lt;code&gt;{poll}&lt;/code&gt; or &lt;code&gt;{poll return}&lt;/code&gt; in the instruction comments —&lt;br&gt;
this is directly checkable on a running JVM, not just a claim from a blog.&lt;/p&gt;

&lt;p&gt;A second correction worth stating precisely: &lt;strong&gt;"at a safepoint" does not&lt;br&gt;
mean "blocked."&lt;/strong&gt; JNI code, for instance, runs &lt;em&gt;at&lt;/em&gt; a safepoint (its Java&lt;br&gt;
state representation is frozen and safe to inspect) without being&lt;br&gt;
descheduled. "Being blocked always happens at a safepoint, but being at a&lt;br&gt;
safepoint doesn't require being blocked" — this is exactly analogous to&lt;br&gt;
Go's distinction between "blocked safe-points" and the state a goroutine&lt;br&gt;
sits in during a syscall (Part 1.3, category 1): the state is&lt;br&gt;
GC-inspectable without the thread being paused by anything external.&lt;/p&gt;

&lt;p&gt;Two further points that generalize the model correctly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Global vs. per-thread safepoints.&lt;/strong&gt; The page-protection trick (3.5.2)
is a &lt;em&gt;global&lt;/em&gt; mechanism — OpenJDK brings all threads to a safepoint
together for STW work. Some JVMs (Azul's Zing, via what it calls
"Checkpoints") can bring an &lt;em&gt;individual&lt;/em&gt; thread to a safepoint-like state
without a global pause, for short per-thread operations. This is the
same axis Go and BEAM sit on opposite ends of: Go's STW GC phases are
global by necessity (Part 1.3), BEAM's per-process GC is inherently
per-thread already (Part 3) — Zing's Checkpoints are HotSpot borrowing
the BEAM-shaped idea onto a shared-heap runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unsafe code and safepoint density.&lt;/strong&gt; Because a safepoint can occur
between any two bytecodes by default, JNI/Unsafe code that wants to run
long stretches without a safepoint (e.g. a large &lt;code&gt;Unsafe.copyMemory&lt;/code&gt;)
must opt in to periodic safepoint opportunities itself — otherwise a
long native call can inflate the "time to safe-point" the VM thread
waits on before starting STW work, which is precisely the TTSP latency
the linked "Java Peak Safepoints" article measures in production.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  3.5.2b &lt;code&gt;safepoint.cpp&lt;/code&gt;'s &lt;code&gt;begin()&lt;/code&gt; function
&lt;/h3&gt;

&lt;p&gt;Read directly:&lt;br&gt;
&lt;a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/safepoint.cpp" rel="noopener noreferrer"&gt;&lt;code&gt;src/hotspot/share/runtime/safepoint.cpp&lt;/code&gt;&lt;/a&gt;,&lt;br&gt;
&lt;code&gt;SafepointSynchronize::begin()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;SafepointSynchronize&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;is_VM_thread&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"Only VM thread may execute a safepoint"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;log_trace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safepoint&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="s"&gt;"Blocking threads from starting/exiting"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;Threads_lock&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;_waiting_to_block&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nof_threads&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="n"&gt;log_trace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safepoint&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="s"&gt;"Arming safepoint using %s wait barrier"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_wait_barrier&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="n"&gt;arm_safepoint&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SafepointTimeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;safepoint_limit_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SafepointTracing&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;start_of_safepoint&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jlong&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;SafepointTimeoutDelay&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;NANOSECS_PER_MILLISEC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and further down, what happens if a thread doesn't reach the poll in time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AbortVMOnSafepointTimeout&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;elapsedTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;MILLIUNITS&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AbortVMOnSafepointTimeoutDelay&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JavaThreadIteratorWithHandle&lt;/span&gt; &lt;span class="n"&gt;jtiwh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;JavaThread&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cur_thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jtiwh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cur_thread&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;safepoint_state&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;is_running&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;VMError&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;set_safepoint_timed_out_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cur_thread&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;signal_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cur_thread&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SIGILL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"blocking a safepoint"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; &lt;code&gt;begin()&lt;/code&gt; grabs &lt;code&gt;Threads_lock&lt;/code&gt; so no thread can start or exit&lt;br&gt;
mid-arming, then calls &lt;code&gt;arm_safepoint()&lt;/code&gt; (the page-protect operation from&lt;br&gt;
3.5.2), and waits, tracked via &lt;code&gt;_waiting_to_block&lt;/code&gt;. HotSpot also ships a&lt;br&gt;
built-in watchdog (&lt;code&gt;SafepointTimeout&lt;/code&gt;/&lt;code&gt;AbortVMOnSafepointTimeoutDelay&lt;/code&gt;)&lt;br&gt;
that, past a configurable delay, forcibly sends &lt;code&gt;SIGILL&lt;/code&gt; to whichever&lt;br&gt;
thread hasn't reached its poll — a thread can, in production, simply fail&lt;br&gt;
to reach a safepoint in reasonable time, and the VM's answer is to crash&lt;br&gt;
that thread deliberately rather than hang forever. This is the closest&lt;br&gt;
direct JVM analogue to the Go pre-1.14 failure mode (Part 1.3): HotSpot&lt;br&gt;
has an explicit, source-visible failsafe for it, while Go's answer was to&lt;br&gt;
change the preemption mechanism so the stuck case couldn't occur at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open question:&lt;/strong&gt; what &lt;code&gt;arm_safepoint()&lt;/code&gt;'s wait-barrier implementation&lt;br&gt;
does at the OS level (mentioned as &lt;code&gt;_wait_barrier-&amp;gt;description()&lt;/code&gt; but not&lt;br&gt;
read here) — the next step to fully trace the mechanism from "STW&lt;br&gt;
requested" to "all threads confirmed parked."&lt;/p&gt;
&lt;h3&gt;
  
  
  3.5.3 Why Go didn't reuse this approach
&lt;/h3&gt;

&lt;p&gt;Go's runtime doesn't have a JIT emitting per-loop poll instructions the way&lt;br&gt;
HotSpot's C1/C2 compilers do — Go compiles ahead-of-time, and the&lt;br&gt;
project's own tried-and-rejected loop-back-edge check (Part 1.3) is&lt;br&gt;
functionally the &lt;em&gt;interpreter-style&lt;/em&gt; version of HotSpot's mechanism minus&lt;br&gt;
the page-fault trick: an explicit conditional branch at every loop&lt;br&gt;
iteration, not a trap-backed memory read. That 7.8% regression is&lt;br&gt;
consistent with paying interpreter-tier overhead in what should be&lt;br&gt;
compiled-tier code — HotSpot avoids this cost by making the poll a load&lt;br&gt;
the branch predictor and speculative execution can mostly hide, and by only&lt;br&gt;
weaponizing it into an actual trap at the moment a safepoint is requested,&lt;br&gt;
not on every iteration unconditionally. Go's signal-based async safe-points&lt;br&gt;
sidestep the problem differently: instead of a poll executed by the&lt;br&gt;
goroutine itself, the OS interrupts the thread from outside, at zero&lt;br&gt;
steady-state cost, and the runtime only pays for stack-map lookups on the&lt;br&gt;
rare occasions a preemption is actually requested.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.5.4 Checkpoint
&lt;/h3&gt;

&lt;p&gt;Both HotSpot's polling and Go's pre-1.14 back-edge checks impose steady-state&lt;br&gt;
cost on every loop iteration. What's the qualitative difference in &lt;em&gt;how&lt;/em&gt;&lt;br&gt;
they impose it, and why does that explain why one shipped and the other&lt;br&gt;
was measured at 7.8% and rejected? (Answer: HotSpot's poll is a single&lt;br&gt;
memory load against a page the CPU can usually predict/cache — cheap even&lt;br&gt;
paid unconditionally, and JIT-compiled so it's tuned per hot loop; Go's&lt;br&gt;
back-edge check was a comparison-and-branch against a stack guard variable,&lt;br&gt;
inserted by a non-JIT ahead-of-time compiler across &lt;em&gt;all&lt;/em&gt; loops uniformly,&lt;br&gt;
without HotSpot's runtime profiling to place it selectively.)&lt;/p&gt;


&lt;h2&gt;
  
  
  Part 4 — Comparison: the safe-point problem, side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Go&lt;/th&gt;
&lt;th&gt;Kotlin (JVM)&lt;/th&gt;
&lt;th&gt;BEAM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Concurrency unit&lt;/td&gt;
&lt;td&gt;Goroutine (runtime-managed stack)&lt;/td&gt;
&lt;td&gt;Coroutine (compiler-transformed continuation)&lt;/td&gt;
&lt;td&gt;Process (VM-managed, isolated heap)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who controls switching&lt;/td&gt;
&lt;td&gt;Go runtime scheduler (GMP)&lt;/td&gt;
&lt;td&gt;Compiler-generated state machine + dispatcher&lt;/td&gt;
&lt;td&gt;BEAM scheduler (per-core)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preemption style&lt;/td&gt;
&lt;td&gt;Non-cooperative (signal, SIGURG) constrained by safe-points&lt;/td&gt;
&lt;td&gt;Cooperative (suspend points only)&lt;/td&gt;
&lt;td&gt;Non-cooperative (reduction counting)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory model&lt;/td&gt;
&lt;td&gt;Shared heap across goroutines&lt;/td&gt;
&lt;td&gt;Shared heap across coroutines (same JVM heap)&lt;/td&gt;
&lt;td&gt;Isolated heap per process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GC scope&lt;/td&gt;
&lt;td&gt;Whole-process; STW phases need all goroutines at a safe-point&lt;/td&gt;
&lt;td&gt;Whatever JVM GC is running; safe-points are a separate JIT-polling mechanism unrelated to coroutines&lt;/td&gt;
&lt;td&gt;Per-process; one process's GC pause never affects others&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What can go wrong if preempted incorrectly&lt;/td&gt;
&lt;td&gt;GC sees stale/invalid pointer, collects live object&lt;/td&gt;
&lt;td&gt;N/A — nothing is force-preempted&lt;/td&gt;
&lt;td&gt;N/A — no shared state to corrupt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault containment&lt;/td&gt;
&lt;td&gt;A panic in one goroutine can crash the process unless recovered&lt;/td&gt;
&lt;td&gt;An exception in one coroutine can propagate per structured-concurrency scope&lt;/td&gt;
&lt;td&gt;A crash is isolated to one process by construction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;The one-sentence unification:&lt;/strong&gt; the safe-point problem exists exactly&lt;br&gt;
where preemption is non-cooperative &lt;em&gt;and&lt;/em&gt; memory is shared — Go has both&lt;br&gt;
and pays for it with stack maps and signal handling; BEAM has non-cooperative&lt;br&gt;
preemption but no shared memory, so no safe-point problem exists; Kotlin has&lt;br&gt;
shared memory but no non-cooperative preemption of coroutines, so it also&lt;br&gt;
sidesteps the problem, at the cost of coroutines being unable to preempt&lt;br&gt;
themselves out of a runaway loop.&lt;/p&gt;


&lt;h2&gt;
  
  
  Part 5 — GraalVM: does its safepoint mechanism actually differ from HotSpot's?
&lt;/h2&gt;

&lt;p&gt;Source read directly:&lt;br&gt;
&lt;a href="https://github.com/oracle/graal/blob/master/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Safepoint.java" rel="noopener noreferrer"&gt;&lt;code&gt;oracle/graal&lt;/code&gt; → &lt;code&gt;substratevm/src/com.oracle.svm.core/.../thread/Safepoint.java&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;GraalVM Native Image (SubstrateVM) does ahead-of-time compilation of JVM&lt;br&gt;
bytecode to a native binary, replacing HotSpot entirely. It changes startup&lt;br&gt;
time and memory footprint substantially, and removes JIT-recompilation-&lt;br&gt;
triggered safepoints (no JIT running concurrently). It does &lt;strong&gt;not&lt;/strong&gt; change&lt;br&gt;
the Kotlin coroutine suspension model — the CPS transform happens in the&lt;br&gt;
Kotlin compiler before GraalVM ever sees bytecode (Part 2). But the&lt;br&gt;
safepoint mechanism itself — the thing Part 3.5 covered for HotSpot — is&lt;br&gt;
genuinely different in SubstrateVM, not just a reimplementation of the same&lt;br&gt;
idea, which is worth being precise about since it's easy to assume "still&lt;br&gt;
the JVM, so still a poll page."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Safepoint.java&lt;/code&gt;'s own class-level doc comment, quoted because the&lt;br&gt;
mechanism it describes is the whole finding:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Manages the initiation of safepoints. A safepoint is a global state where all Java threads,
 * except one, are paused so that invasive operations (such as a garbage collection) can execute
 * without interferences.
 *
 * When a safepoint is requested, one Java thread (the master) acquires the
 * {@link VMThreads#SAFEPOINT_MUTEX}. The master notifies all other threads about the pending
 * safepoint by modifying each thread's {@link SafepointCheckCounter} thread-local.
 *
 * Each Java threads periodically checks the value of its {@link SafepointCheckCounter}. If a
 * safepoint is pending, the thread enters the safepoint slowpath and blocks on the mutex that the
 * master is holding.
 */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the actual arming operation, which negates rather than page-protects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SafepointCheckCounter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getVolatile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="nc"&gt;SafepointCheckCounter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compareAndSet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; HotSpot arms a safepoint by protecting a shared&lt;br&gt;
&lt;em&gt;memory page&lt;/em&gt; (Part 3.5.2) — every thread's poll is a load against the same&lt;br&gt;
address, and the trap is a hardware page fault. SubstrateVM arms a&lt;br&gt;
safepoint by &lt;strong&gt;negating each thread's own counter value&lt;/strong&gt; via CAS — there&lt;br&gt;
is no shared page, no page fault, no signal involved in the common path at&lt;br&gt;
all. Each compiled method decrements its thread-local&lt;br&gt;
&lt;code&gt;SafepointCheckCounter&lt;/code&gt; periodically (mirroring HotSpot's loop-back-edge/&lt;br&gt;
method-entry poll &lt;em&gt;placement&lt;/em&gt; from Part 3.5.2a, but checking a counter&lt;br&gt;
going negative instead of reading a protected page); when a thread notices&lt;br&gt;
its own counter went negative, it takes the slowpath and blocks on&lt;br&gt;
&lt;code&gt;SAFEPOINT_MUTEX&lt;/code&gt;. This is architecturally closer to BEAM's reduction&lt;br&gt;
counting (Part 3.3) than to HotSpot's own poll-page trap — a per-thread&lt;br&gt;
software counter, not a shared trap-triggering memory region — even though&lt;br&gt;
SubstrateVM is still fundamentally a JVM-semantics runtime, not a&lt;br&gt;
process-isolated one. It's a genuine mechanism swap under the same&lt;br&gt;
"safepoint" name, not a cosmetic difference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open question:&lt;/strong&gt; why Oracle's SubstrateVM engineers chose a per-thread&lt;br&gt;
counter over reusing HotSpot's poll-page approach — the file doesn't&lt;br&gt;
state the rationale. Plausible candidates (avoiding &lt;code&gt;mprotect&lt;/code&gt; syscalls&lt;br&gt;
per arm/disarm cycle, better behavior under isolates/multi-tenant native&lt;br&gt;
images) are not confirmed by the source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Checkpoint:&lt;/strong&gt; if your bottleneck is coroutine scheduling latency, does&lt;br&gt;
GraalVM help? (Answer: no — that's a compile-time property of the Kotlin&lt;br&gt;
coroutine transform, orthogonal to which VM executes the resulting&lt;br&gt;
bytecode.) Separately: does GraalVM's safepoint mechanism reduce&lt;br&gt;
"time to safepoint" compared to HotSpot's for the same reason BEAM's does?&lt;br&gt;
(Answer: not for the same reason — BEAM's advantage is structural, no&lt;br&gt;
shared memory to protect at all; SubstrateVM still has a shared heap and&lt;br&gt;
still needs a global stop, it's only changed &lt;em&gt;how&lt;/em&gt; each thread notices&lt;br&gt;
the request, not the fact that all threads must eventually be stopped&lt;br&gt;
together.)&lt;/p&gt;


&lt;h2&gt;
  
  
  Part 6 — The GC algorithms behind the flags
&lt;/h2&gt;

&lt;p&gt;The copy of &lt;em&gt;The Garbage Collection Handbook&lt;/em&gt; at &lt;code&gt;~/Documents/Books_on_vm/&lt;/code&gt;&lt;br&gt;
is the &lt;strong&gt;2023 second edition&lt;/strong&gt;, and it covers G1, ZGC, and Shenandoah by&lt;br&gt;
name with dedicated sections. What follows is read directly from that PDF&lt;br&gt;
(page numbers are the book's own page numbers, printed in each page&lt;br&gt;
header — the PDF file's own page index has a fixed +37 offset from these,&lt;br&gt;
front matter accounts for the difference).&lt;/p&gt;
&lt;h3&gt;
  
  
  6.1 The tricolor abstraction: base algorithm before the formal invariant
&lt;/h3&gt;

&lt;p&gt;Source: Robert Nystrom, &lt;a href="https://craftinginterpreters.com/garbage-collection.html" rel="noopener noreferrer"&gt;&lt;em&gt;Crafting Interpreters&lt;/em&gt;, "Garbage Collection"&lt;/a&gt;&lt;br&gt;
(also read in this research thread — see &lt;code&gt;003_research.md&lt;/code&gt; in this&lt;br&gt;
directory). Nystrom's chapter gives the base mark-sweep tricolor algorithm&lt;br&gt;
in intuitive terms, before the GC Handbook's formal correctness theorem&lt;br&gt;
below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Start off with all objects white.
2. Find all the roots and mark them gray.
3. Repeat as long as there are still gray objects:
    a. Pick a gray object. Turn any white objects that the object
       mentions to gray.
    b. Mark the original gray object black.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The "gray wavefront" image: roots turn gray, each gray object's neighbors&lt;br&gt;
turn gray while the object itself turns black, and the wavefront advances&lt;br&gt;
through the graph leaving reached (black) objects behind it, sweeping&lt;br&gt;
white-and-untouched objects up as garbage once no gray objects remain.&lt;/p&gt;

&lt;p&gt;&lt;a href="/img/posts/concurrency-and-gc_tricolor-trace.png" class="article-body-image-wrapper"&gt;&lt;img src="/img/posts/concurrency-and-gc_tricolor-trace.png" alt="tricolor mark-sweep wavefront"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Image: Robert Nystrom, Crafting Interpreters, "Garbage Collection"&lt;br&gt;
(craftinginterpreters.com/garbage-collection.html), used here per the&lt;br&gt;
site's stated terms for educational reference.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  6.1a The formal version of the same invariant
&lt;/h3&gt;

&lt;p&gt;The Go runtime's own version of this invariant, from&lt;br&gt;
&lt;a href="https://github.com/golang/go/blob/master/src/runtime/mgc.go" rel="noopener noreferrer"&gt;&lt;code&gt;src/runtime/mgc.go&lt;/code&gt;&lt;/a&gt;,&lt;br&gt;
&lt;code&gt;gcStart&lt;/code&gt;'s STW-to-mark-phase transition — the full sequence, not just the&lt;br&gt;
one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="c"&gt;// Enter concurrent mark phase and enable&lt;/span&gt;
    &lt;span class="c"&gt;// write barriers.&lt;/span&gt;
    &lt;span class="c"&gt;//&lt;/span&gt;
    &lt;span class="c"&gt;// Because the world is stopped, all Ps will&lt;/span&gt;
    &lt;span class="c"&gt;// observe that write barriers are enabled by&lt;/span&gt;
    &lt;span class="c"&gt;// the time we start the world and begin&lt;/span&gt;
    &lt;span class="c"&gt;// scanning.&lt;/span&gt;
    &lt;span class="c"&gt;//&lt;/span&gt;
    &lt;span class="c"&gt;// Write barriers must be enabled before assists are&lt;/span&gt;
    &lt;span class="c"&gt;// enabled because they must be enabled before&lt;/span&gt;
    &lt;span class="c"&gt;// any non-leaf heap objects are marked. Since&lt;/span&gt;
    &lt;span class="c"&gt;// allocations are blocked until assists can&lt;/span&gt;
    &lt;span class="c"&gt;// happen, we want to enable assists as early as&lt;/span&gt;
    &lt;span class="c"&gt;// possible.&lt;/span&gt;
    &lt;span class="n"&gt;setGCPhase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_GCmark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;gcBgMarkPrepare&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// Must happen before assists are enabled.&lt;/span&gt;
    &lt;span class="n"&gt;gcPrepareMarkRoots&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Mark all active tinyalloc blocks. Since we're&lt;/span&gt;
    &lt;span class="c"&gt;// allocating from these, they need to be black like&lt;/span&gt;
    &lt;span class="c"&gt;// other allocations. The alternative is to blacken&lt;/span&gt;
    &lt;span class="c"&gt;// the tiny block on every allocation from it, which&lt;/span&gt;
    &lt;span class="c"&gt;// would slow down the tiny allocator.&lt;/span&gt;
    &lt;span class="n"&gt;gcMarkTinyAllocs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// At this point all Ps have enabled the write&lt;/span&gt;
    &lt;span class="c"&gt;// barrier, thus maintaining the no white to&lt;/span&gt;
    &lt;span class="c"&gt;// black invariant. Enable mutator assists to&lt;/span&gt;
    &lt;span class="c"&gt;// put back-pressure on fast allocating&lt;/span&gt;
    &lt;span class="c"&gt;// mutators.&lt;/span&gt;
    &lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gcBlackenEnabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read top to bottom, this is the actual order of operations at the start&lt;br&gt;
of a mark phase: (1) &lt;code&gt;setGCPhase(_GCmark)&lt;/code&gt; flips the global phase while&lt;br&gt;
the world is still stopped, so every P will see the new phase the instant&lt;br&gt;
it resumes; (2) &lt;code&gt;gcBgMarkPrepare()&lt;/code&gt; and &lt;code&gt;gcPrepareMarkRoots()&lt;/code&gt; set up the&lt;br&gt;
background mark workers and root-scanning job queue &lt;em&gt;before&lt;/em&gt; anything is&lt;br&gt;
allowed to mark, per the comment's own stated ordering constraint (write&lt;br&gt;
barriers before assists, assists before marking); (3) &lt;code&gt;gcMarkTinyAllocs()&lt;/code&gt;&lt;br&gt;
retroactively blackens small allocations already carved out of the tiny&lt;br&gt;
allocator, so they don't need individual barrier coverage; (4) only then&lt;br&gt;
does &lt;code&gt;atomic.Store(&amp;amp;gcBlackenEnabled, 1)&lt;/code&gt; actually arm the write barrier&lt;br&gt;
Part 1.3a's &lt;code&gt;mbarrier.go&lt;/code&gt; checks — the comment's "no white to black&lt;br&gt;
invariant" claim is true starting at this exact store, not before it.&lt;br&gt;
Everything above this line is setup that must complete first because the&lt;br&gt;
invariant would be violated if the barrier were armed before roots were&lt;br&gt;
prepared.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;§15.1, "The tricolour abstraction, revisited"&lt;/strong&gt; (p.331) restates this&lt;br&gt;
exact comment as a general, two-condition correctness theorem, attributed&lt;br&gt;
to Wilson [1994]:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Condition 1: the mutator stores a pointer to a white object into a black object, and
Condition 2: all paths from any grey objects to that white object are destroyed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; an object only gets incorrectly collected if&lt;br&gt;
&lt;em&gt;both&lt;/em&gt; conditions hold simultaneously — a black object gains a hidden&lt;br&gt;
white pointer, &lt;em&gt;and&lt;/em&gt; every path the collector could have used to&lt;br&gt;
rediscover that white object independently gets cut. Break either&lt;br&gt;
condition and the collector stays correct. This immediately generalizes&lt;br&gt;
into two named invariants, quoted directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The weak tricolour invariant: All white objects pointed to by a black
object are grey protected (that is, reachable from some grey object,
either directly or through a chain of white objects).

The strong tricolour invariant: There are no pointers from black
objects to white objects.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; Go's hybrid barrier (Yuasa deletion + Dijkstra insertion) is&lt;br&gt;
an example of a barrier that enforces something between these two — Go's&lt;br&gt;
comment said "no white to black," which is the strong invariant stated&lt;br&gt;
for Go's specific non-moving collector. Non-moving collectors like Go's&lt;br&gt;
can get away with only the weak invariant ("white pointers in black&lt;br&gt;
objects are not a problem because their grey protected white targets are&lt;br&gt;
eventually shaded"), while concurrent copying collectors must preserve&lt;br&gt;
the strong invariant, because a moving collector discards the fromspace&lt;br&gt;
white copy entirely at cycle end — a black tospace object still pointing&lt;br&gt;
at a discarded fromspace address is a dangling pointer, not just a late&lt;br&gt;
mark. This is the precise reason ZGC/Shenandoah (both moving) and Go&lt;br&gt;
(non-moving) need structurally different barrier strength.&lt;/p&gt;
&lt;h3&gt;
  
  
  6.2 §11.6 — the vendor-agnostic version of Part 3.5
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;§11.6, "GC safe-points and mutator suspension"&lt;/strong&gt; (p.198) makes a&lt;br&gt;
distinction Part 3.5 didn't have a name for: &lt;strong&gt;GC safe-points vs. GC&lt;br&gt;
check-points.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Many systems make the opposite choice and only allow garbage collection
at certain restricted safe-points, and only produce maps for those
points. The minimal set of safe-points needed for correctness includes
each allocation ... and each call of a routine in which there may be
allocation or which may cause the thread to suspend in a wait.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Since these additional safe-points do not do anything that actually can
trigger a garbage collection, they need to have an added check for
whether garbage collection is needed/requested, so we call them GC
check-points.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; a safe-point is &lt;em&gt;any&lt;/em&gt; point where the machine&lt;br&gt;
state is GC-inspectable; a check-point is the (much smaller) subset where&lt;br&gt;
the runtime has also inserted code to actually test "should I stop right&lt;br&gt;
now." HotSpot's poll-page instructions (Part 3.5.2) are check-points in&lt;br&gt;
this vocabulary — they're placed at loop back-edges and method entries,&lt;br&gt;
which the book independently identifies as the correct minimal placement&lt;br&gt;
("there needs to be a safe-point in each loop; a simple rule is to place a&lt;br&gt;
safe-point at each backwards branch in a function... in addition there&lt;br&gt;
needs to be a safe-point in each function entry or each return"). This is&lt;br&gt;
the same placement rule Gil Tene's account (Part 3.5.2a) gave for HotSpot&lt;br&gt;
specifically — the book states it as the general principle HotSpot (and&lt;br&gt;
Go's old back-edge check, and SubstrateVM's counter) are all independent&lt;br&gt;
instances of.&lt;/p&gt;

&lt;p&gt;The book also names the exact two mechanisms this document found in two&lt;br&gt;
different runtimes and treats them as one general dichotomy, citing&lt;br&gt;
Agesen [1998]:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agesen [1998] compared two ways of causing a thread to suspend at a
GC-point. One is polling ... The other technique is patching, which
involves modifying the code at the next GC-point(s) of the thread.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; HotSpot's poll-page (Part 3.5.2) is "polling."&lt;br&gt;
Go's pre-1.14 stack-bound-check poisoning (Part 1.3) is closer to&lt;br&gt;
"patching" in spirit — the runtime rewrites &lt;code&gt;stackguard0&lt;/code&gt; so the &lt;em&gt;existing&lt;/em&gt;&lt;br&gt;
bound check becomes a trap, rather than adding a new check. Go's post-1.14&lt;br&gt;
&lt;code&gt;SIGURG&lt;/code&gt; mechanism is neither in Agesen's original two-way split — it's a&lt;br&gt;
third strategy (async external interrupt) the 1998 taxonomy predates,&lt;br&gt;
which is a useful thing to know precisely: SIGURG isn't a variant of&lt;br&gt;
polling or patching, it's a genuinely later addition to the design space.&lt;/p&gt;
&lt;h3&gt;
  
  
  6.3 G1 — the actual phase list, not just the region idea
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;§16.5, "Garbage-First: collecting young and old regions"&lt;/strong&gt; (p.354)&lt;br&gt;
gives OpenJDK 20's actual mixed-collection phase sequence, not just the&lt;br&gt;
region-prioritization idea Part 6's original version described. Quoting&lt;br&gt;
the phase list directly (abridged):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Start: This stop-the-world phase is piggy-backed on a 'young only'
collection that seeds the roots for concurrent marking. ... G1 sets a
Top At Mark Start (TAMS) variable for each region; any objects allocated
at addresses above the TAMS for a region will be considered implicitly
live/marked.

Concurrent Mark From Roots: ... using a snapshot-at-the-beginning
algorithm, with mutators using a deletion barrier ... G1 marking threads
... Each thread first claims a region to mark by atomically advancing a
global 'finger' to the right.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; TAMS is the mechanism that lets G1 avoid&lt;br&gt;
marking newly-allocated objects at all — anything allocated after&lt;br&gt;
marking starts in a region is &lt;em&gt;assumed&lt;/em&gt; live by address comparison alone,&lt;br&gt;
no barrier or scan needed for it. This is a genuinely different strategy&lt;br&gt;
from Go's approach (Part 1.3a), where new allocations are marked black&lt;br&gt;
immediately via &lt;code&gt;gcBlackenEnabled&lt;/code&gt;-gated behavior — G1's TAMS threshold&lt;br&gt;
achieves a similar goal (don't waste work on new objects) through a&lt;br&gt;
different mechanism (an address comparison per region, not a global&lt;br&gt;
allocation-color flag). The book also names G1's barrier as a &lt;strong&gt;deletion&lt;/strong&gt;&lt;br&gt;
barrier specifically (snapshot-at-the-beginning), which is the Yuasa half&lt;br&gt;
only, not Go's hybrid Yuasa+Dijkstra combination — a real algorithmic&lt;br&gt;
difference between the two collectors' barriers not visible from the JEP&lt;br&gt;
alone.&lt;/p&gt;
&lt;h3&gt;
  
  
  6.4 ZGC — colored pointers, confirmed with the actual bit layout
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;§17.5&lt;/strong&gt; describes ZGC [Lidén, 2018] and gives the actual pointer bit&lt;br&gt;
layout (Figure 17.4) rather than just naming "colored pointers" as a&lt;br&gt;
concept:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ZGC uses tagged pointers and self-healing load barriers. ... A group of
four higher-order bits determine a colour. The four bits are named F
(finalisable), R (relocated), M1 and M0 (marked). A given pointer will
have only one of R, M0 and M1 set ... At any given time, only one of R,
M0 or M1 is the good colour; loading pointers of other colours forces a
load barrier slow path.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; "colored pointer" isn't metaphorical — it's four specific&lt;br&gt;
bits (F/R/M0/M1) living in the unused high bits of a 64-bit pointer, on a&lt;br&gt;
system where the actual address only needs 47 bits (the address bits span&lt;br&gt;
roughly bit 46 down to 0, leaving room above for tag bits). Self-healing:&lt;br&gt;
when a thread loads a pointer with a stale colour, the load barrier fixes&lt;br&gt;
the colour in place via the barrier's slow path, so subsequent loads of&lt;br&gt;
that same memory location by the same thread don't re-trigger the slow&lt;br&gt;
path. This is a materially different mechanism from both Go's write&lt;br&gt;
barrier (fires on writes, not reads) and G1's deletion barrier (also&lt;br&gt;
write-side) — ZGC's barrier fires on reads.&lt;/p&gt;
&lt;h3&gt;
  
  
  6.5 Shenandoah
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;§17.5, "Shenandoah"&lt;/strong&gt; (p.401) gives both the algorithm and, notably, a&lt;br&gt;
sentence where the cited authors question their own earlier design&lt;br&gt;
argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;In 2016 Flood et al. argued that programs such as web caches hold onto
objects just long enough to defeat generational collectors so, instead,
Shenandoah focuses its effort on regions with fewer live objects...
They also argued that generational collection requires some kind of
remembered set... However, it is not clear that these concerns still
hold and implementation of a generational version of Shenandoah is in
progress.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; as of 2023, Shenandoah's own foundational 2016 design&lt;br&gt;
argument (why not to be generational) is under active reconsideration by&lt;br&gt;
its own authors, with a generational variant in progress — a different&lt;br&gt;
picture than a static "Shenandoah is non-generational, ZGC is&lt;br&gt;
non-generational" fact would give; the field is mid-revision on this&lt;br&gt;
point, not settled.&lt;/p&gt;

&lt;p&gt;The self-healing load barrier itself, given as actual pseudocode&lt;br&gt;
(Algorithm 17.12), is worth comparing line-by-line against ZGC's:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nc"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isGCactive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;             &lt;span class="o"&gt;/*&lt;/span&gt; &lt;span class="n"&gt;fast&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;accesses&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt; &lt;span class="o"&gt;*/&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isInCollectionSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;
    &lt;span class="n"&gt;fwd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;resolveForwardee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="o"&gt;/*&lt;/span&gt; &lt;span class="n"&gt;access&lt;/span&gt; &lt;span class="n"&gt;forwarding&lt;/span&gt; &lt;span class="n"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="o"&gt;*/&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="err"&gt;≠&lt;/span&gt; &lt;span class="n"&gt;fwd&lt;/span&gt;
        &lt;span class="nc"&gt;CAS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fwd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;         &lt;span class="o"&gt;/*&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;heal&lt;/span&gt; &lt;span class="o"&gt;*/&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fwd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; Shenandoah's self-healing works by CAS-ing the&lt;br&gt;
&lt;em&gt;forwarding pointer&lt;/em&gt; back into the original slot once discovered — a&lt;br&gt;
different self-healing mechanism than ZGC's colored-pointer remap, but the&lt;br&gt;
same goal (pay the barrier cost once per location, not once per access).&lt;br&gt;
The &lt;code&gt;isGCactive()&lt;/code&gt; fast-path check is functionally the same shape as G1's&lt;br&gt;
&lt;code&gt;gcBlackenEnabled&lt;/code&gt; and Go's &lt;code&gt;gcphase == _GCmark&lt;/code&gt; gating (Part 1.3a) — all&lt;br&gt;
three collectors pay for their respective barrier machinery only during an&lt;br&gt;
active cycle, confirmed independently now in three different codebases and&lt;br&gt;
this one textbook description.&lt;/p&gt;
&lt;h3&gt;
  
  
  6.6 Compressor and Pauseless/C4: page protection vs. tagged pointers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;§17.5, "Compressor"&lt;/strong&gt; (p.386) and &lt;strong&gt;"Pauseless and C4"&lt;/strong&gt; (p.387–388).&lt;/p&gt;

&lt;p&gt;Compressor's mechanism, from the book's own category list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Live: pages containing (mostly) live objects
Condemned: pages containing some live objects, but mostly dead ones
Free: pages currently free but available for allocation
New Live: pages in which copied live objects have been allocated but not yet copied
Dead: unmapped pages that can be recycled once there are no pointers to them
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; Compressor drives compaction entirely through page&lt;br&gt;
protection — it &lt;code&gt;mprotect&lt;/code&gt;s tospace pages so any mutator access traps,&lt;br&gt;
and the trap handler performs forwarding/copying on demand. This is the&lt;br&gt;
same style of mechanism as HotSpot's safepoint poll page (Part 3.5.2) —&lt;br&gt;
a hardware trap doing useful work, not just a stop signal — applied to&lt;br&gt;
compaction correctness rather than safepoint arrival: the same trick&lt;br&gt;
generalizes to enforcing GC invariants during copying, not just to&lt;br&gt;
synchronizing mutator suspension.&lt;/p&gt;

&lt;p&gt;Pauseless/C4's mechanism, by contrast, avoids page protection almost&lt;br&gt;
entirely and uses &lt;strong&gt;tagged pointers&lt;/strong&gt; instead — confirmed by the actual&lt;br&gt;
bit layout (Figure 17.2, p.388):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pauseless steals one address bit from the 64-bit address space to use as
a pointer tag. This Not-Marked-Through (NMT) bit is used by the LVB
during the concurrent marking phase of the collector to decide whether
the reference has previously been scanned by the collector.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; C4/Pauseless's LVB ("Loaded Value Barrier") is functionally&lt;br&gt;
the same idea as ZGC's self-healing colored-pointer load barrier&lt;br&gt;
(Part 6.4) — both steal address bits, both self-heal on load. ZGC's&lt;br&gt;
design (2018) comes after and builds on Azul's Pauseless/C4 (2005/2011),&lt;br&gt;
not as an independent invention. C4's generational extension steals an&lt;br&gt;
additional tag bit per pointer to track which generation the referent&lt;br&gt;
belongs to, letting young and old collections proceed independently&lt;br&gt;
without cross-checking a single global NMT value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open question:&lt;/strong&gt; whether ZGC's generational variant (Figure 17.4b) has&lt;br&gt;
an analogous bit-budget tradeoff to C4's — not verified against ZGC's&lt;br&gt;
generational tagged-pointer figure directly.&lt;/p&gt;
&lt;h3&gt;
  
  
  6.7 Staccato
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;§19.7, "Staccato: best-effort compaction with mutator wait-freedom"&lt;/strong&gt;&lt;br&gt;
(p.455) — the McCloskey et al. paper also referenced in&lt;br&gt;
&lt;code&gt;006_other_reading_materials.md&lt;/code&gt; of this research directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Staccato [McCloskey et al., 2008] permits concurrent compaction without
requiring the mutators to use locks or atomic operations like
compare-and-swap in the common case, even on multiprocessors with weak
memory ordering.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mechanism, confirmed via the book's own pseudocode (Algorithm 19.8):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;copyObjects&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt;
        &lt;span class="nc"&gt;CompareAndSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nf"&gt;forwardingAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;COPYING&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;waitForRaggedSynch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;writeFence&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;readFence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; "ragged synchronisation" is the general name for a pattern&lt;br&gt;
this document already touched via Collie's "pre-compaction ragged&lt;br&gt;
handshake" (Part 6.6) and §15.3's "Ragged phase changes": instead of a&lt;br&gt;
single global stop-the-world rendezvous, each mutator independently&lt;br&gt;
performs a memory fence "at regular intervals (such as GC safe-points),"&lt;br&gt;
and the collector waits for all mutators to have crossed that fence&lt;br&gt;
individually, at their own pace — a distributed handshake rather than a&lt;br&gt;
synchronized barrier. This is a different STW-avoidance strategy than&lt;br&gt;
HotSpot's single global &lt;code&gt;Threads_lock&lt;/code&gt;-based rendezvous (Part 3.5.2b).&lt;/p&gt;
&lt;h3&gt;
  
  
  6.8 Scope note and further reading
&lt;/h3&gt;

&lt;p&gt;Not covered above: Ch.14 (parallel GC internals — marking, copying,&lt;br&gt;
compaction algorithms), Tax-and-Spend (§19.6). Sapphire, Transactional&lt;br&gt;
Sapphire, Platinum, Metronome, Stopless, Chicken, and Clover are covered&lt;br&gt;
in the appendix below (§19.5–19.7), not in the numbered sections above.&lt;/p&gt;

&lt;p&gt;Vendor/JEP-level pointers, for the production-flag view rather than the&lt;br&gt;
algorithm view:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://openjdk.org/jeps/248" rel="noopener noreferrer"&gt;JEP 248 — Make G1 the Default Garbage Collector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openjdk.org/jeps/439" rel="noopener noreferrer"&gt;JEP 439 — Generational ZGC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openjdk.org/jeps/318" rel="noopener noreferrer"&gt;JEP 318 — Epsilon: A No-Op Garbage Collector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.openjdk.org/display/zgc/Main" rel="noopener noreferrer"&gt;OpenJDK ZGC wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.openjdk.org/display/shenandoah/Main" rel="noopener noreferrer"&gt;OpenJDK Shenandoah wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/MemoryManagement.md" rel="noopener noreferrer"&gt;GraalVM Native Image &lt;code&gt;MemoryManagement.md&lt;/code&gt;&lt;/a&gt; — &lt;code&gt;--gc=serial|G1|epsilon&lt;/code&gt; flags&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  6.9 Sapphire, Platinum, Metronome, Stopless, Chicken, Clover
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Sapphire and Transactional Sapphire&lt;/strong&gt; (§17.4, p.376–379) — a concurrent&lt;br&gt;
copying algorithm for shared-memory multiprocessors that lets one mutator&lt;br&gt;
thread at a time flip from reading fromspace to reading tospace, rather&lt;br&gt;
than stopping all threads to flip together. Transactional Sapphire&lt;br&gt;
extends this with parallel collector threads and hardware/software&lt;br&gt;
transactions for object copying, moving through four phase groups (Mark,&lt;br&gt;
Copy, Flip, Reclaim — Algorithm 17.4) with distinct write barriers per&lt;br&gt;
phase.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Flip: In this group, the collector forwards pointers in global variables
and thread stacks and registers, flipping them one at a time into
tospace. Unflipped mutator threads may hold references to both fromspace
and tospace copies (even of the same object).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; incremental flipping (one thread at a time, rather than a&lt;br&gt;
single stop-the-world flip) is the mechanism's whole point — it trades a&lt;br&gt;
more complex barrier (mutators may see both copies of the same object&lt;br&gt;
simultaneously) for a shorter window where any thread needs to block.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Platinum&lt;/strong&gt; (§17.4, p.383–384) — mostly-concurrent, generational,&lt;br&gt;
replicating, aimed at long tail latencies. Uses fewer collector threads&lt;br&gt;
than cores and binds each to a specific core, and uses Intel's memory&lt;br&gt;
protection keys (not a syscall-heavy &lt;code&gt;mprotect&lt;/code&gt;, but a fast per-thread&lt;br&gt;
register) to give collector and mutator threads different access rights&lt;br&gt;
to the same pages without a full page-table change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Platinum sets up two protection keys. During collection, one is
associated with pages to which only the collector threads should have
write access, and Platinum arranges that the mutators set their
protection keys to disallow writes to these pages.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Metronome&lt;/strong&gt; (§19.5, p.439–441) — a time-based real-time collector for&lt;br&gt;
Java: an incremental mark-sweep collector with partial on-demand&lt;br&gt;
compaction, scheduled via fixed time quanta (500µs collector slices in a&lt;br&gt;
10ms window) to guarantee a minimum mutator utilization (MMU) target,&lt;br&gt;
commonly 70%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stopless&lt;/strong&gt; (§19.7, p.454–455) — a lock-free concurrent compactor. Rather&lt;br&gt;
than requiring mutators to update both a fromspace and tospace copy of an&lt;br&gt;
object (as Sapphire does), Stopless enforces that exactly one copy is&lt;br&gt;
ever the definitive one, tracked via a double-word compare-and-swap on a&lt;br&gt;
"wide" intermediate copy with a status word per field&lt;br&gt;
(&lt;code&gt;inOriginal&lt;/code&gt;/&lt;code&gt;inWide&lt;/code&gt;/&lt;code&gt;inCopy&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chicken&lt;/strong&gt; (§19.7, p.458) — architecturally close to Staccato (Part 6.7),&lt;br&gt;
developed independently, targeting x86/x86-64's stronger memory model&lt;br&gt;
specifically. Because that architecture orders reads relative to atomics,&lt;br&gt;
only writes need to abort an in-progress copy, and the ragged&lt;br&gt;
synchronisation Staccato relies on doesn't need the read-fence half.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clover&lt;/strong&gt; (§19.7, p.458–459) — guarantees compaction with lock-free&lt;br&gt;
mutator access in the common case by having the collector mark&lt;br&gt;
just-copied fields with a reserved sentinel value α (chosen to make&lt;br&gt;
collision with a real program value astronomically unlikely, using a&lt;br&gt;
128-bit compare-and-swap on modern x86-64). A mutator that reads α knows&lt;br&gt;
to reload the field through the forwarding pointer instead.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 7 — Reading this material in passes, not linearly
&lt;/h2&gt;

&lt;p&gt;The framework below is applied selectively, not as a checklist run against&lt;br&gt;
every source uniformly — some of these materials don't have all five&lt;br&gt;
properties (a rejected-alternatives section, an admitted weak spot), and&lt;br&gt;
forcing the framework onto a source that doesn't have that structure would&lt;br&gt;
produce a false pattern rather than an honest one. Where a technique&lt;br&gt;
doesn't fit a given source, it's left out for that source rather than&lt;br&gt;
padded in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The one dominant constraint&lt;/strong&gt; applies cleanly to Go (GC needs pointer&lt;br&gt;
maps → constrains preemption, Part 1.3), to BEAM (shared-nothing →&lt;br&gt;
no safe-point problem, Part 3.2–3.3a), and to Kotlin (can't modify the&lt;br&gt;
JVM → CPS transform is the only lever, Part 2.1). It applies less cleanly&lt;br&gt;
to the GC algorithms in Part 6 — G1, ZGC, and Shenandoah are all solving&lt;br&gt;
the &lt;em&gt;same&lt;/em&gt; constraint (bound pause time independent of heap size) with&lt;br&gt;
different mechanisms, so for that cluster the useful move is comparing&lt;br&gt;
mechanisms against a shared constraint, not finding a different&lt;br&gt;
constraint for each.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An admitted weak spot / rejected path&lt;/strong&gt; is present, concretely, in three&lt;br&gt;
places already surfaced in this document, not hypothetically: Go's&lt;br&gt;
proposal rejecting loop-back-edge checks over a measured 7.8% regression&lt;br&gt;
(Part 1.3), HotSpot's own &lt;code&gt;AbortVMOnSafepointTimeout&lt;/code&gt; watchdog admitting a&lt;br&gt;
thread can simply fail to reach a safepoint in production (Part 3.5.2b),&lt;br&gt;
and Shenandoah's authors' 2023-dated uncertainty about their own 2016&lt;br&gt;
non-generational argument (Part 6.5). These three are the highest-value&lt;br&gt;
re-reads in the whole document if you only have time to revisit three&lt;br&gt;
things.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.1 Concept mindmap — the tension map, not a fact list
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mindmap
  root((Preempt a running&amp;lt;br/&amp;gt;task safely))
    Who initiates preemption
      OS timer interrupt
        No cooperation needed
        Thread has no say
      Go: SIGURG signal
        Async, external
        Still gated by safe-point check
      HotSpot: poll page
        Thread checks itself
        Trap turns it involuntary
      SubstrateVM: counter CAS
        Per-thread, no shared page
      BEAM: reduction counter
        Every instruction dispatch
        No separate check needed
    What makes a point unsafe
      Shared GC-managed heap
        Pointer vs raw int ambiguity
        Go stack maps
        HotSpot stack maps
      No shared heap at all
        BEAM sidesteps the question
        Every point is already safe
    GC correctness invariant
      Weak tricolour
        Non-moving OK
        Go hybrid barrier
      Strong tricolour
        Required for moving GC
        ZGC load barrier
        Shenandoah load barrier
        G1 deletion barrier
    Concurrency unit
      Go: goroutine, CSP channels
      Kotlin: continuation, CPS
        compiler layer
        kotlinx.coroutines layer
      BEAM: process, shared nothing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this diagram is arguing, in one sentence per branch:&lt;/strong&gt; preemption&lt;br&gt;
mechanisms differ in &lt;em&gt;who&lt;/em&gt; decides to stop a task; safety mechanisms&lt;br&gt;
differ in &lt;em&gt;whether&lt;/em&gt; shared GC-managed memory makes most points unsafe to&lt;br&gt;
stop at; GC correctness reduces to which tricolour invariant a collector&lt;br&gt;
can afford to only weakly enforce, which is itself downstream of whether&lt;br&gt;
the collector moves objects. The three top-level branches are not&lt;br&gt;
independent — the "who initiates" answer each language chose was&lt;br&gt;
constrained by the "what makes a point unsafe" answer their memory model&lt;br&gt;
already committed them to.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.2 Reading roadmap — four passes across this document's actual source list
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TD
    subgraph P1["Pass 1 — Orient: what problem is being solved"]
        direction LR
        A1["Go proposal 24543&amp;lt;br/&amp;gt;abstract + problem statement"]
        A2["mgc.go header comment&amp;lt;br/&amp;gt;(algorithm summary, lines 1-20)"]
        A3["Safepoint.java class doc&amp;lt;br/&amp;gt;(GraalVM)"]
        A4["GC Handbook §1.3&amp;lt;br/&amp;gt;Comparing GC algorithms"]
    end

    subgraph P2["Pass 2 — Find the one constraint"]
        direction LR
        B1["preempt.go safe-point&amp;lt;br/&amp;gt;categories comment"]
        B2["signal_unix.go SIGURG&amp;lt;br/&amp;gt;4-criteria rationale"]
        B3["Continuation.kt&amp;lt;br/&amp;gt;+ why suspend is compiler-level"]
        B4["erl_process.h fcalls comment&amp;lt;br/&amp;gt;+ Part 3.2 shared-nothing"]
        B5["GC Handbook §11.6&amp;lt;br/&amp;gt;safe-point vs check-point"]
    end

    subgraph P3["Pass 3 — Map rejected paths and tensions"]
        direction LR
        C1["Go: loop back-edge check,&amp;lt;br/&amp;gt;7.8% regression, rejected"]
        C2["HotSpot: SafepointTimeout&amp;lt;br/&amp;gt;watchdog admits failure mode"]
        C3["Shenandoah §17.5: authors&amp;lt;br/&amp;gt;question own 2016 argument"]
        C4["Agesen 1998: polling vs&amp;lt;br/&amp;gt;patching vs SIGURG (3rd path)"]
    end

    subgraph P4["Pass 4 — Compress to one sentence per system"]
        direction LR
        D1["Go: non-cooperative,&amp;lt;br/&amp;gt;gated by pointer safety"]
        D2["Kotlin: cooperative by&amp;lt;br/&amp;gt;construction, no interrupt exists"]
        D3["BEAM: no unsafe points&amp;lt;br/&amp;gt;exist, only a budget"]
        D4["JVM family: same constraint&amp;lt;br/&amp;gt;(bounded pause), different&amp;lt;br/&amp;gt;mechanism per collector"]
    end

    P1 --&amp;gt; P2 --&amp;gt; P3 --&amp;gt; P4

    A1 -.reread after.-&amp;gt; C1
    B2 -.reread after.-&amp;gt; C4
    B5 -.reread after.-&amp;gt; C2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;How to actually use this roadmap:&lt;/strong&gt; don't read every box once in order —&lt;br&gt;
Pass 1 across all four sources first (just enough to know what problem&lt;br&gt;
each is solving), then Pass 2 across all four (find the load-bearing&lt;br&gt;
constraint in each), then Pass 3 only for the sources that have a rejected&lt;br&gt;
path or admitted gap (not all of them do — Continuation.kt, for instance,&lt;br&gt;
doesn't have a "we tried X and rejected it" moment the way Go's proposal&lt;br&gt;
or Shenandoah's retrospective do), then Pass 4 forces you to write the&lt;br&gt;
one-sentence compression for each system, which is the actual test of&lt;br&gt;
whether the first three passes worked. If you can't write Pass 4's&lt;br&gt;
sentence for a system, that's the signal to go back to Pass 2 for that&lt;br&gt;
system specifically, not to reread everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  Source references for going deeper (only if a checkpoint above stumps you)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Primary source files fetched and read directly for this document
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Go&lt;/strong&gt; (golang/go, master):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/golang/go/blob/master/src/runtime/preempt.go" rel="noopener noreferrer"&gt;&lt;code&gt;src/runtime/preempt.go&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/golang/go/blob/master/src/runtime/signal_unix.go" rel="noopener noreferrer"&gt;&lt;code&gt;src/runtime/signal_unix.go&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/golang/go/blob/master/src/runtime/mgc.go" rel="noopener noreferrer"&gt;&lt;code&gt;src/runtime/mgc.go&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/golang/go/blob/master/src/runtime/proc.go" rel="noopener noreferrer"&gt;&lt;code&gt;src/runtime/proc.go&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/golang/go/blob/master/src/runtime/mbarrier.go" rel="noopener noreferrer"&gt;&lt;code&gt;src/runtime/mbarrier.go&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Kotlin/kotlinx.coroutines&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JetBrains/kotlin → &lt;a href="https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/kotlin/coroutines/Continuation.kt" rel="noopener noreferrer"&gt;&lt;code&gt;libraries/stdlib/src/kotlin/coroutines/Continuation.kt&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;JetBrains/kotlin → &lt;a href="https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/kotlin/coroutines/intrinsics/Intrinsics.kt" rel="noopener noreferrer"&gt;&lt;code&gt;libraries/stdlib/src/kotlin/coroutines/intrinsics/Intrinsics.kt&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Kotlin/kotlinx.coroutines → &lt;a href="https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt" rel="noopener noreferrer"&gt;&lt;code&gt;kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Kotlin/kotlinx.coroutines → &lt;a href="https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt" rel="noopener noreferrer"&gt;&lt;code&gt;kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Kotlin/kotlinx.coroutines → &lt;a href="https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt" rel="noopener noreferrer"&gt;&lt;code&gt;kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;(attempted, 404) &lt;code&gt;.../intrinsics/CoroutineIntrinsics.kt&lt;/code&gt; — doesn't exist at that path, discarded&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;OpenJDK/HotSpot&lt;/strong&gt; (openjdk/jdk, master):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/safepoint.hpp" rel="noopener noreferrer"&gt;&lt;code&gt;src/hotspot/share/runtime/safepoint.hpp&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/safepointMechanism.hpp" rel="noopener noreferrer"&gt;&lt;code&gt;src/hotspot/share/runtime/safepointMechanism.hpp&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/safepoint.cpp" rel="noopener noreferrer"&gt;&lt;code&gt;src/hotspot/share/runtime/safepoint.cpp&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;GraalVM/SubstrateVM&lt;/strong&gt; (oracle/graal, master):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/oracle/graal/blob/master/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Safepoint.java" rel="noopener noreferrer"&gt;&lt;code&gt;substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Safepoint.java&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/MemoryManagement.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/reference-manual/native-image/MemoryManagement.md&lt;/code&gt;&lt;/a&gt; — official docs, &lt;code&gt;--gc=serial|G1|epsilon&lt;/code&gt; flags and defaults&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Garbage Collection Handbook, 2nd ed. (2023) — read directly, local PDF
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;~/Documents/Books_on_vm/Jones, Richard - The Garbage Collection Handbook...pdf&lt;/code&gt;&lt;br&gt;
(Jones, Hosking, Moss; Chapman and Hall/CRC, 2023). Sections read directly&lt;br&gt;
for Part 6: §11.6 "GC safe-points and mutator suspension" (p.198), §15.1&lt;br&gt;
"Correctness of concurrent collection" / tricolour invariants (p.331–334),&lt;br&gt;
§16.5 "Garbage-First: collecting young and old regions" (p.354), §17.4&lt;br&gt;
"Replication copying" — Sapphire, Transactional Sapphire, Platinum&lt;br&gt;
(p.376–384), §17.5 "Concurrent compaction" — Compressor (p.386),&lt;br&gt;
Pauseless/C4 (p.387–388), Collie (p.395), ZGC (p.396), Shenandoah (p.401),&lt;br&gt;
§19.5 "Metronome" (p.439–441), §19.7 "Controlling fragmentation" —&lt;br&gt;
Staccato, Stopless, Chicken, Clover (p.453–459). Not yet read directly:&lt;br&gt;
Ch.14 (parallel GC internals), §19.6 "Tax-and-Spend" (p.448).&lt;/p&gt;

&lt;h3&gt;
  
  
  GC algorithms (Part 6) — not read as source, cited as pointers for later reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://openjdk.org/jeps/248" rel="noopener noreferrer"&gt;JEP 248 — Make G1 the Default Garbage Collector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openjdk.org/jeps/439" rel="noopener noreferrer"&gt;JEP 439 — Generational ZGC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openjdk.org/jeps/318" rel="noopener noreferrer"&gt;JEP 318 — Epsilon: A No-Op Garbage Collector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.openjdk.org/display/zgc/Main" rel="noopener noreferrer"&gt;OpenJDK ZGC wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.openjdk.org/display/shenandoah/Main" rel="noopener noreferrer"&gt;OpenJDK Shenandoah wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Detlefs, Flood, Heller, Printezis, "Garbage-First Garbage Collection" (ISMM 2004) — search title, not linked directly&lt;/li&gt;
&lt;li&gt;Flood, Ross, Shipilev et al., "Shenandoah: An Open-Source Concurrent Compacting Garbage Collector for OpenJDK" (2016) — search title, not linked directly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Erlang/OTP&lt;/strong&gt; (erlang/otp, master):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/erlang/otp/blob/master/erts/emulator/beam/erl_process.c" rel="noopener noreferrer"&gt;&lt;code&gt;erts/emulator/beam/erl_process.c&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/erlang/otp/blob/master/erts/emulator/beam/erl_process.h" rel="noopener noreferrer"&gt;&lt;code&gt;erts/emulator/beam/erl_process.h&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/erlang/otp/blob/master/erts/emulator/beam/erl_vm.h" rel="noopener noreferrer"&gt;&lt;code&gt;erts/emulator/beam/erl_vm.h&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/erlang/otp/blob/master/erts/emulator/beam/emu/beam_emu.c" rel="noopener noreferrer"&gt;&lt;code&gt;erts/emulator/beam/emu/beam_emu.c&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/erlang/otp/blob/master/erts/emulator/beam/erl_gc.c" rel="noopener noreferrer"&gt;&lt;code&gt;erts/emulator/beam/erl_gc.c&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/erlang/otp/blob/master/erts/emulator/beam/erl_bif_info.c" rel="noopener noreferrer"&gt;&lt;code&gt;erts/emulator/beam/erl_bif_info.c&lt;/code&gt;&lt;/a&gt; (grep only, minimal use)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Crafting Interpreters — read directly (via &lt;code&gt;003_research.md&lt;/code&gt;, this directory)
&lt;/h3&gt;

&lt;p&gt;Robert Nystrom, &lt;a href="https://craftinginterpreters.com/garbage-collection.html" rel="noopener noreferrer"&gt;&lt;em&gt;Crafting Interpreters&lt;/em&gt;, "Garbage Collection"&lt;/a&gt;&lt;br&gt;
— base mark-sweep and tricolor abstraction (Part 6.1); the tricolor&lt;br&gt;
wavefront image used in Part 6.1 is sourced from this chapter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secondary sources (pasted/cited, not independently fetched for this doc)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Sunny Srinidhi, &lt;a href="https://medium.com/software-under-the-hood/under-the-hood-java-peak-safepoints-dd45af07d766" rel="noopener noreferrer"&gt;"Under the Hood: Java Peak Safepoints"&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Nitsan Wakart / Gil Tene (mechanical-sympathy list), &lt;a href="http://psy-lob-saw.blogspot.com/2015/12/safepoints.html" rel="noopener noreferrer"&gt;"Safepoints: Meaning, Side Effects and Overheads"&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="http://chriskirk.blogspot.com/2013/09/what-is-java-safepoint.html" rel="noopener noreferrer"&gt;"What is a Java Safepoint?"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://jpbempel.blogspot.com/2013/03/safety-first-safepoints.html" rel="noopener noreferrer"&gt;"Safety First: Safepoints"&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>kotlin</category>
      <category>erlang</category>
      <category>garbagecollection</category>
    </item>
    <item>
      <title>Optimux: on the fly image/video optimizer</title>
      <dc:creator>Alex Day</dc:creator>
      <pubDate>Mon, 10 Aug 2026 12:27:51 +0000</pubDate>
      <link>https://dev.to/silvern47/optimux-on-the-fly-imagevideo-optimizer-47a8</link>
      <guid>https://dev.to/silvern47/optimux-on-the-fly-imagevideo-optimizer-47a8</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/go-batteries/optimux" rel="noopener noreferrer"&gt;Optimux&lt;/a&gt; is a Go service that resizes, re-encodes, and streams images and video on the fly — &lt;code&gt;/resize?image_url=...&amp;amp;sizes=300x0&amp;amp;format=webp&amp;amp;quality=60&lt;/code&gt;, done. It's been running as an internal service for a while. I'm open-sourcing it now, AGPL-3.0, because the worker pool underneath it — the part that decides how many goroutines are doing image processing at any given moment — ended up being the most interesting thing in the codebase, and it deserves to exist somewhere other than a private repo.&lt;/p&gt;

&lt;p&gt;This is the story of how that worker pool got there, including the part where the pattern everyone starts with turned out to be actively wrong for this workload.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting point: the Job/Dispatcher pattern
&lt;/h2&gt;

&lt;p&gt;Like a lot of people who've had to build a Go job queue, I started from Marcio Castilho's &lt;a href="https://medium.com/smsjunk/handling-1-million-requests-per-minute-with-golang-f70ac505fcaa" rel="noopener noreferrer"&gt;Handling 1 Million Requests per Minute with Golang&lt;/a&gt;. Worth being precise about what that article actually builds, because the mismatch with image processing turned out to matter a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;Job&lt;/code&gt; struct wraps one unit of work (there, a POST payload headed to S3).&lt;/li&gt;
&lt;li&gt;A buffered &lt;code&gt;JobQueue chan Job&lt;/code&gt; receives incoming jobs.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;Dispatcher&lt;/code&gt; owns a &lt;code&gt;WorkerPool chan chan Job&lt;/code&gt; — a &lt;strong&gt;channel of channels&lt;/strong&gt;. Each &lt;code&gt;Worker&lt;/code&gt; has its own &lt;code&gt;chan Job&lt;/code&gt;, and the moment a worker finishes a job, it pushes its &lt;em&gt;own&lt;/em&gt; channel back into the shared &lt;code&gt;WorkerPool&lt;/code&gt;. The dispatcher's loop just pulls one worker-channel off &lt;code&gt;WorkerPool&lt;/code&gt; and hands it the next job.&lt;/li&gt;
&lt;li&gt;No worker ever polls for work or reports "I'm free" via a flag — availability &lt;em&gt;is&lt;/em&gt; "my channel is currently sitting in the pool." &lt;code&gt;MAX_WORKERS&lt;/code&gt; and &lt;code&gt;MAX_QUEUE&lt;/code&gt; are env-configured, fixed for the process lifetime.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The article's numbers are genuinely good: they took a system that needed ~100 EC2 instances down to 4 &lt;code&gt;c4.Large&lt;/code&gt; instances handling close to a million requests a minute, by replacing unbounded goroutine spawning with this bounded pool. But the workload is uploading JSON payloads to S3 — almost entirely I/O wait, barely any CPU. I built the same pattern, pointed it at image resizing instead, and it did not translate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern didn't fit, and pprof proved it
&lt;/h2&gt;

&lt;p&gt;I wired up the Job/Dispatcher pattern for image jobs and it was slow in a way that didn't make sense from the throughput numbers alone. So I pulled a blocking profile, and it was unambiguous:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;pprof blocking profile shows &lt;strong&gt;67.99%&lt;/strong&gt; of total time spent waiting on &lt;code&gt;chanrecv1&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's workers blocked waiting for jobs, and the dispatcher blocked waiting for workers to hand their channel back — a pattern built for a workload where a worker is &lt;em&gt;cheap and fast&lt;/em&gt; per unit (fire an S3 PUT, wait on the network, done) turns into mostly idle channel choreography when a unit of work is &lt;em&gt;hundreds of milliseconds of CPU-bound image processing&lt;/em&gt; instead. The channel-of-channels handoff, the dispatcher loop, the per-job registration — all of that overhead is invisible when a worker's actual job takes microseconds of your attention and the rest is network wait. It stops being invisible when the job itself is the bottleneck. Target throughput was &lt;code&gt;8.6rps&lt;/code&gt;; no matter what I tuned in this shape, it sat at &lt;code&gt;2.0xrps&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So I threw it out. Not tuned, not patched — removed, and rebuilt from a synchronous baseline up, one variable at a time, so I'd actually know what each change bought me instead of tuning inside a pattern that was wrong from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rebuilding from zero, one variable at a time
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Synchronous baseline, no workers at all&lt;/strong&gt; — inline resize, respond directly. &lt;code&gt;2.9rps&lt;/code&gt;. Already ahead of the dispatcher pattern's &lt;code&gt;2.0x&lt;/code&gt;, which was the first sign the problem wasn't "not enough workers," it was the shape of the concurrency itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single channel, single worker.&lt;/strong&gt; The smallest thing that still queues — one goroutine pulling off one channel, closer to a single core churning a work list than to a "pool" in any real sense. &lt;code&gt;2.1rps&lt;/code&gt; — &lt;em&gt;worse&lt;/em&gt; than doing nothing concurrent at all. Concurrency has a floor cost, and this workload was paying it without buying anything back yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single queue, 4 workers.&lt;/strong&gt; &lt;code&gt;3.15rps&lt;/code&gt;, average latency &lt;code&gt;1.37s&lt;/code&gt;. First real win, and a small one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two queues — fetch and process, split apart — 4 workers each.&lt;/strong&gt; &lt;code&gt;3.07rps&lt;/code&gt;, marginally &lt;em&gt;lower&lt;/em&gt; throughput than the single queue, but the latency distribution across percentiles visibly smoothed out. Tracing explained why: fetching a source image from tmpfs cost &lt;code&gt;40-100ms&lt;/code&gt;; processing it (libvips, resizing a 3.1MB source down to a 120×240 webp) cost &lt;code&gt;~700ms&lt;/code&gt;. Those are two stages with wildly different service times sharing a queue and worker pool — an impedance mismatch, where a burst of cheap fetches can queue up behind a slow processor, or the reverse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two queues, but weighted 2 fetchers / 4 processors&lt;/strong&gt;, leaning into the imbalance on purpose. Performed &lt;em&gt;worse&lt;/em&gt; than the even split, still around &lt;code&gt;3+rps&lt;/code&gt;. My read at the time: this isn't a batch pipeline where you can freely over-provision the expensive stage and let a queue absorb the mismatch — it's a real-time request path where the caller is still on the other end of the HTTP connection waiting, so over-provisioning one stage just moves the queueing, it doesn't remove it.&lt;/p&gt;

&lt;p&gt;I also tried a demand-driven producer/consumer setup along the way — closer to Elixir's GenStage, where the consumer explicitly asks the producer for N items instead of the producer pushing whenever it has something. It didn't pan out for this workload, and honestly the specifics didn't survive in my notes — only the conclusion did: the demand/ack round-trip was adding coordination cost that a plain buffered channel already got for free, without a corresponding improvement in how work actually got scheduled.&lt;/p&gt;

&lt;p&gt;Separately, before any dynamic scaler existed, I added something orthogonal to worker count entirely: instead of buffering the whole encoded image and writing it in one response, the handler started flushing early and streaming bytes out as the encoder produced them. That alone moved the needle independent of worker shape — &lt;code&gt;3.28rps&lt;/code&gt;, latency down to &lt;code&gt;1.31s&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually generalizes, and what doesn't
&lt;/h2&gt;

&lt;p&gt;Here's the actual lesson, not just the numbers: the Job/Dispatcher pattern isn't wrong, it's scoped to a specific kind of workload — one where the &lt;em&gt;external&lt;/em&gt; resource (S3, a network call, a database) is the thing you're rationing, and your own CPU is basically idle waiting on it. In that world, a large fixed worker count is nearly free — you're just capping how many outstanding waits you allow. libvips-backed image processing inverts that: the constrained resource is &lt;em&gt;your own CPU and memory&lt;/em&gt;, fetch and process have genuinely different cost profiles, and a fixed &lt;code&gt;MAX_WORKERS&lt;/code&gt; picked once has no way to track a queue that swings between bursty and empty. &lt;code&gt;iostat&lt;/code&gt; on the EC2 box backed this up directly — CPU usage swinging from 80%+ down to 40% inside the same short window, &lt;code&gt;%steal&lt;/code&gt; spiking as high as 8.74% (hypervisor-stolen cycles, which no amount of worker retuning fixes). A workload like that needs the worker count itself to be a live variable, not a constant — which is what actually motivated moving off any fixed-size pool entirely, GenStage-shaped or otherwise, toward something that watches queue depth and scales.&lt;/p&gt;

&lt;p&gt;It's worth being precise about &lt;em&gt;where&lt;/em&gt; the two patterns actually diverge structurally, because it's not just "one has a scaler and one doesn't."&lt;/p&gt;

&lt;p&gt;The dispatcher pattern's availability signal is a second layer of indirection: each worker owns a private &lt;code&gt;chan Job&lt;/code&gt;, and "I'm free" is expressed by pushing that channel into a shared &lt;code&gt;WorkerPool chan chan Job&lt;/code&gt;. That's &lt;code&gt;N+1&lt;/code&gt; channels for &lt;code&gt;N&lt;/code&gt; workers — one pool channel plus one per-worker channel — and a dispatcher goroutine whose whole job is shuttling a channel out of the pool, handing it a job, and waiting for it to come back. It's a clean pattern, but it exists to solve a problem optimux's worker pool doesn't have: every worker in &lt;code&gt;DynamicScaler&lt;/code&gt; pulls directly off one shared &lt;code&gt;Queue chan T&lt;/code&gt;. There's no dispatcher goroutine, no per-worker channel, no explicit "I'm free" message at all — a worker's availability &lt;em&gt;is&lt;/em&gt; nothing more than "currently blocked on a receive from &lt;code&gt;Queue&lt;/code&gt;," which Go's own runtime already arbitrates correctly among however many goroutines are competing to receive. The single-shared-queue shape showed up as early as the "single queue, 4 workers" experiment above, well before any scaler existed, and every version since kept it — the channel-of-channels indirection never came back, because a single channel with competing receivers gave equal or better throughput without the extra bookkeeping.&lt;/p&gt;

&lt;p&gt;The other divergence is what each pattern treats as fixed. The article's &lt;code&gt;MAX_WORKERS&lt;/code&gt; is an env var read once at startup — there's no concept of a worker ever leaving the pool short of the whole process dying, because the workload it targets doesn't need one: S3 upload capacity doesn't really shrink at 2am. &lt;code&gt;DynamicScaler&lt;/code&gt; has an explicit opposite belief baked into its registry — every &lt;code&gt;WorkerSlot&lt;/code&gt; can be marked &lt;code&gt;Retiring&lt;/code&gt; and needs a defined, race-free way to leave the pool without dropping in-flight work, because for this workload the &lt;em&gt;right&lt;/em&gt; worker count at any moment is itself the thing under contention (CPU cores, memory for concurrently-decoded images), not a constant you get to pick once.&lt;/p&gt;

&lt;h2&gt;
  
  
  What DynamicScaler does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;DynamicScaler[T]&lt;/code&gt; (&lt;code&gt;src/mediahose/schedulers.go&lt;/code&gt;) is a generic worker pool that grows and shrinks based on queue pressure instead of running a fixed goroutine count. The type parameter is the job type, so the same scaler independently drives image jobs, video jobs, and batch jobs off different queues with different thresholds.&lt;/p&gt;

&lt;p&gt;The registry is a single ordered slice, append-only in creation order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;WorkerSlot&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Idx&lt;/span&gt;      &lt;span class="kt"&gt;int64&lt;/span&gt;
    &lt;span class="n"&gt;Retiring&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="c"&gt;// marked for retirement; reclaimed only on exit notify&lt;/span&gt;
    &lt;span class="n"&gt;worker&lt;/span&gt;   &lt;span class="n"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;cancel&lt;/span&gt;   &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CancelFunc&lt;/span&gt;
    &lt;span class="n"&gt;retire&lt;/span&gt;   &lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;DynamicScaler&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;WorkerFactory&lt;/span&gt;      &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;Queue&lt;/span&gt;              &lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;
    &lt;span class="n"&gt;MinWorkers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MaxWorkers&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;ScaleUpThreshold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ScaleDownThreshold&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;CheckInterval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ScaleCooldown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RetireGrace&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;
    &lt;span class="n"&gt;workers&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WorkerSlot&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c"&gt;// tail = newest&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the slice is append-only, the tail is always the newest worker — which makes "retire the newest worker first" (LIFO) a deterministic scan instead of a random pick. There's no separate counter tracking how many workers are active; the live count is derived on read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;countLive&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;workers&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WorkerSlot&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;live&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wsl&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;workers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;wsl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Retiring&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;live&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;live&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing else exists to fall out of sync with the registry, because there's nothing else — the count is a computation over the one piece of state, not a second piece of state someone has to remember to update alongside it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retirement happens at an idle boundary, not on demand.&lt;/strong&gt; A worker that opts in (&lt;code&gt;FetchWorker&lt;/code&gt;, the image/video processing worker, does) runs a single blocking &lt;code&gt;select&lt;/code&gt; over three cases — context cancellation, a retire request, and the job queue — with no &lt;code&gt;default&lt;/code&gt; branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;retireReq&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;fw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RetireCh&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;retireReq&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;jobQueueChan&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;// process it&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;default&lt;/code&gt; matters: the worker is genuinely parked, doing nothing, only while blocked in that select. A retire request lands the instant the worker goes idle, not after it happens to finish whatever job comes next. When the scaler decides to shrink the pool, it marks the newest slot &lt;code&gt;Retiring: true&lt;/code&gt; and sends a one-shot ack request; the slot stays in the registry, still counted toward the hard &lt;code&gt;MaxWorkers&lt;/code&gt; cap, until the worker actually exits and notifies the scaler on a close channel. There's a bounded grace period (default 2s) — if the worker hasn't idled out by then, the scaler cancels its context as a fallback nudge, but removal from the registry only ever happens on the real exit notification, never on the timeout itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scale decisions are cooled down, with an escape hatch.&lt;/strong&gt; The signal driving &lt;code&gt;scale()&lt;/code&gt; is &lt;code&gt;len(ds.Queue)&lt;/code&gt; — instantaneous backlog, sampled on a ticker. That's not the same thing as, say, SQS handing you &lt;code&gt;ApproximateNumberOfMessagesVisible&lt;/code&gt; alongside an in-flight count, smoothed over a window before an alarm fires. A raw instantaneous queue length is noisy under bursty traffic: it can cross &lt;code&gt;ScaleUpThreshold&lt;/code&gt; and &lt;code&gt;ScaleDownThreshold&lt;/code&gt; within a couple of ticks without the underlying load actually having changed, and a scaler with no way to tell a real trend from a blip would happily add a worker and retire it right back. The cooldown (default 30s between scale actions) exists because that better signal doesn't exist yet — it's a blunt fix for a noisy input, not a fundamental property of dynamic pools. The one case that bypasses it entirely: if live workers ever drop below &lt;code&gt;MinWorkers&lt;/code&gt;, the scaler refills immediately regardless of cooldown, so a crash storm can't leave the pool sitting empty for a full cooldown window waiting on a timer that doesn't care capacity is gone.&lt;/p&gt;

&lt;p&gt;That's the whole mechanism: one ordered registry as the single source of truth for both "who's alive" and "who retires next," idle-boundary handshakes instead of blind cancellation, and a cooldown standing in for a smarter scaling signal that isn't built yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I didn't test: prefork
&lt;/h2&gt;

&lt;p&gt;One pattern from a different corner of the same problem space I never actually tried: &lt;a href="https://docs.gofiber.io/" rel="noopener noreferrer"&gt;Fiber's prefork mode&lt;/a&gt; — instead of one Go process handling everything under a single &lt;code&gt;GOMAXPROCS&lt;/code&gt;, use &lt;code&gt;SO_REUSEPORT&lt;/code&gt; to run N forked OS processes, each getting its own slice of cores, with the kernel load-balancing connections across them.&lt;/p&gt;

&lt;p&gt;My honest guess is it wouldn't have moved the throughput numbers above. Every pool-shape experiment — single worker, 4 workers, split queues, weighted split — barely moved throughput (&lt;code&gt;2.1&lt;/code&gt; to &lt;code&gt;3.15rps&lt;/code&gt;), because the real cost is &lt;code&gt;~700ms&lt;/code&gt; of libvips CPU work per image, not goroutine-scheduling or channel overhead. Prefork changes how many OS processes share a machine's cores, not how many cores exist; the same physical CPU is doing the same total amount of resize work whether it's one process running N goroutines or K processes each running fewer, and Go's scheduler already multiplexes goroutines across cores efficiently for CPU-bound work like this.&lt;/p&gt;

&lt;p&gt;Where I'd actually expect it to help is somewhere none of these benchmarks were looking: tail latency and fault isolation. This worker loop allocates large image byte slices per job (there's a &lt;code&gt;byts = nil&lt;/code&gt; in it as a GC hint) — a GC pause or heavy mark-assist period in one process adds jitter to &lt;em&gt;every&lt;/em&gt; concurrent request in that process. With prefork, GC pressure in one fork doesn't stall requests being served by a sibling fork. Same story for a pathological input (corrupt file, huge dimensions) wedging or OOMing one process — the kernel keeps routing new connections to the survivors instead of the whole fleet going down. Neither of those is a throughput property, and neither is something &lt;code&gt;Progression.md&lt;/code&gt;'s numbers (throughput, average latency) would have caught even if true. Genuinely untested — not a claim, a gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Three things this codebase is deliberately waiting on or missing rather than half-building now, documented in the repo for anyone who wants to pick them up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streaming instead of buffering, once govips ships it.&lt;/strong&gt; Every image today gets fully buffered into a &lt;code&gt;[]byte&lt;/code&gt; at each stage — load, encode, respond — before the next stage starts. govips just merged native streaming support (&lt;code&gt;LoadImageFromReader&lt;/code&gt;, &lt;code&gt;SaveToWriter*&lt;/code&gt;, PR &lt;a href="https://github.com/davidbyttow/govips/pull/539" rel="noopener noreferrer"&gt;davidbyttow/govips#539&lt;/a&gt;), which would let a load and an encode work directly against &lt;code&gt;io.Reader&lt;/code&gt;/&lt;code&gt;io.Writer&lt;/code&gt; instead of a fully materialized buffer — real memory savings for a service whose job is exactly this: dynamic compression of non-standard sizes at request time. It's not in a tagged release yet (latest tag predates the merge by months), so this is parked as &lt;code&gt;docs/ADR-003&lt;/code&gt; in the repo until that lands, rather than pinned to an untagged commit in something serving production traffic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A real HTTP/2 cross-stream priority scheduler.&lt;/strong&gt; This one came out of a completely different rabbit hole: &lt;a href="https://blog.cloudflare.com/parallel-streaming-of-progressive-images/" rel="noopener noreferrer"&gt;Cloudflare's parallel-streaming-of-progressive-images&lt;/a&gt; work, where a server multiplexing many concurrently-loading images over one HTTP/2 connection sends every image's size header first, then every preview, then remaining refinement data — instead of blasting one image's full body before starting the next. Optimux already does the single-image version of this (&lt;code&gt;StreamEncoder&lt;/code&gt;/&lt;code&gt;ProgressiveStreamEncoder&lt;/code&gt; chunk header→preview→remainder), but the cross-stream scheduling piece — deciding, across every concurrently open image request, whose bytes go out next — turned out to be something almost nobody exposes as a plugin point. Go's &lt;code&gt;x/net/http2.WriteScheduler&lt;/code&gt; is deprecated by its own maintainers ("provides too much visibility into implementation internals, is difficult to use"); nginx has an internal RFC 7540 priority tree that's private to its core module, not reachable from third-party modules; &lt;code&gt;fasthttp/http2&lt;/code&gt; parses the &lt;code&gt;PRIORITY&lt;/code&gt; frame's weight and never reads it again anywhere. The real prior art turned out to be H2O (a genuine O(1) scheduler, but measurably unfair — Tempesta Tech's own simulation found only 2 of 256 streams got ideal scheduling against it), nghttp2 (correct WFQ, but O(n)), and &lt;a href="https://github.com/tempesta-tech/tempesta/pull/1973" rel="noopener noreferrer"&gt;Tempesta FW&lt;/a&gt;, who landed on WFQ backed by HAProxy's &lt;code&gt;ebtree&lt;/code&gt; specifically for the 100-1000-stream range this kind of service would actually see. The plan, written up in &lt;code&gt;docs/EXPLORATION-HTTP2-Priority-Scheduler.md&lt;/code&gt;, is a from-scratch server on &lt;code&gt;golang.org/x/net/http2.Framer&lt;/code&gt; directly — bypassing the deprecated hook entirely — staged from a toy proving the interleaving works, up to real flow control, up to wiring in the actual image pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A scale-up signal that knows when adding a worker won't help.&lt;/strong&gt; &lt;code&gt;scale()&lt;/code&gt; only ever asks "is the queue long" — it has no way to tell a genuine burst apart from a backlog that's building because libvips's own thread pool is already saturated, in which case another Go worker just queues behind the same C-level pool without adding real capacity, while still paying for a decoded image buffer's worth of memory. I went looking for a way to read that saturation directly from govips and it isn't there: &lt;code&gt;ConcurrencyLevel&lt;/code&gt; is write-only in practice (set once via &lt;code&gt;vips_concurrency_set&lt;/code&gt;, read back only for a startup log line), and &lt;code&gt;RuntimeStats&lt;/code&gt; tracks cumulative operation counts, not current thread-pool occupancy. libvips's underlying &lt;code&gt;GThreadPool&lt;/code&gt; (GLib) does expose real introspection — &lt;code&gt;g_thread_pool_unprocessed()&lt;/code&gt;, &lt;code&gt;g_thread_pool_get_num_threads()&lt;/code&gt; — govips just never wraps it. The more practical fix doesn't need any of that: an atomic in-flight counter around each worker's &lt;code&gt;processor.Process()&lt;/code&gt; call, gating &lt;code&gt;addWorkerLocked&lt;/code&gt; when in-flight already matches &lt;code&gt;VipsConcurrency&lt;/code&gt;. Documented as a finding, not built yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why open-source it now
&lt;/h2&gt;

&lt;p&gt;The scaler is the part of this codebase I'd actually want other people to read, argue with, or reuse — a fairly small, self-contained piece of generic Go with real constraints (goroutine lifecycle, backpressure, graceful shutdown) that most worker-pool tutorials, mine included at the start, wave away. The rest of optimux — image/video transforms, S3 wiring, the HTTP surface — is useful but unremarkable by comparison.&lt;/p&gt;

&lt;p&gt;Repo: &lt;strong&gt;&lt;a href="https://github.com/go-batteries/optimux" rel="noopener noreferrer"&gt;github.com/go-batteries/optimux&lt;/a&gt;&lt;/strong&gt;, AGPL-3.0.&lt;/p&gt;

</description>
      <category>go</category>
      <category>programming</category>
    </item>
    <item>
      <title>Shrinking a materials database</title>
      <dc:creator>Alex Day</dc:creator>
      <pubDate>Sun, 09 Aug 2026 14:08:23 +0000</pubDate>
      <link>https://dev.to/silvern47/shrinking-a-materials-database-1g5i</link>
      <guid>https://dev.to/silvern47/shrinking-a-materials-database-1g5i</guid>
      <description>&lt;p&gt;Shrinking datasets is quite contextual. It depends on how the dataset is going to be used. Its the same concept as optimizing databases, based on the read/write patterns.&lt;br&gt;
PACLEC and Traffic Patterns are the key determining factors.&lt;/p&gt;

&lt;p&gt;Hosted on fly.io : &lt;a href="https://materials-db.fly.dev/" rel="noopener noreferrer"&gt;https://materials-db.fly.dev/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The need for this was not to integrate with &lt;a href="https://next-gen.materialsproject.org/materials" rel="noopener noreferrer"&gt;another external API&lt;/a&gt; for retrieving information on different materials and not think about rate-limiting. This was made as a part of a bigger system: &lt;a href="https://ikouchiha47.github.io/p/b8e2c4/materials-science-notes/" rel="noopener noreferrer"&gt;Research Agent for Researchers&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The materials trajectory database was around 12Gigs and is mostly used for training models like &lt;a href="https://github.com/CederGroupHub/chgnet" rel="noopener noreferrer"&gt;CHGNet&lt;/a&gt;. &lt;br&gt;
But I needed it only for responding to query on finding materials and it didn't need all the variations.&lt;/p&gt;

&lt;p&gt;The aim was to be able to serve it using my most loved database - sqlite, in under reasonable timeframe: &amp;lt;500ms.&lt;/p&gt;

&lt;p&gt;A detailed write up is provided in my blog: &lt;a href="https://ikouchiha47.github.io/2026/08/05/materialsdb-11gb-to-266mb.html" rel="noopener noreferrer"&gt;https://ikouchiha47.github.io/2026/08/05/materialsdb-11gb-to-266mb.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>sqlite</category>
      <category>programming</category>
    </item>
    <item>
      <title>Scheduling concurrency</title>
      <dc:creator>Alex Day</dc:creator>
      <pubDate>Sun, 09 Aug 2026 13:36:13 +0000</pubDate>
      <link>https://dev.to/silvern47/scheduling-concurrency-9bd</link>
      <guid>https://dev.to/silvern47/scheduling-concurrency-9bd</guid>
      <description>&lt;p&gt;Have been trying to look at 3 different languages: Go, Kotlin, Elixir/Erlang mostly to understand their concurrency models.&lt;/p&gt;

&lt;p&gt;This article concerns mostly about Go's preemptive scheduling. Looking at the dense article on go: &lt;a href="https://go.googlesource.com/proposal/+/master/design/24543-non-cooperative-preemption.md" rel="noopener noreferrer"&gt;https://go.googlesource.com/proposal/+/master/design/24543-non-cooperative-preemption.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is what I understand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before Go 1.14:  Cooperative preemption at function prologues&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go used compiler-inserted cooperative preemption points in function prologues up to and including Go 1.10. This means Go could only switch between concurrently-executing goroutines at specific points - and the compiler ensured that all local GC roots were known at those safe-points, enabling precise garbage collection.&lt;/p&gt;

&lt;p&gt;The problems this caused were real and serious. In really extreme cases, it could cause a program to halt entirely.&lt;/p&gt;

&lt;p&gt;For example, when a goroutine spinning on an atomic load starved out the goroutine responsible for setting that atomic.&lt;/p&gt;

&lt;p&gt;They tried to fix this by inserting preemption checks at loop back-edges (the obvious next step), but even their most efficient approach - called "fault-based preemption" - added a geomean slowdown of 7.8% on a large benchmark suite. &lt;/p&gt;

&lt;p&gt;It also had implementation downsides: it couldn't target specific threads or goroutines, was "sticky" in that they couldn't resume any loops until all loops were resumed, and interfered with debuggers.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Go 1.14+: Signal-based non-cooperative preemption&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Non-cooperative preemption switches between concurrent execution contexts without explicit preemption checks or assistance from those contexts - the same way modern operating systems switch between threads. Without this, a single poorly-behaved goroutine can wedge a Go application, much like how a single poorly-behaved application could wedge an entire OS.&lt;/p&gt;

&lt;p&gt;The mechanism: Go implements this by sending a POSIX signal to stop a running goroutine and capture its CPU state. If a goroutine is interrupted at a point that must be GC-atomic, the runtime simply resumes the goroutine and tries again later.&lt;/p&gt;

&lt;p&gt;Why SIGURG specifically? It meets all the criteria: it's passed through by debuggers by default, isn't used internally by libc in mixed Go/C binaries, can happen spuriously without consequences, and is extremely unlikely to be used by an application for its real meaning - since out-of-band data is basically unused, and because SIGURG doesn't report which socket has the condition, making it pretty useless for its original purpose.&lt;/p&gt;




&lt;p&gt;SIGURG is a different beast altogether, it sits at the intersection of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;OS signals&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Compiler internals&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Runtime/GC Designs&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It needs more reading. Will come back to that later.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>go</category>
      <category>linux</category>
    </item>
    <item>
      <title>Rebuilding Sofle-choc on android</title>
      <dc:creator>Alex Day</dc:creator>
      <pubDate>Sun, 09 Aug 2026 13:34:28 +0000</pubDate>
      <link>https://dev.to/silvern47/rebuilding-sofle-choc-on-android-8n1</link>
      <guid>https://dev.to/silvern47/rebuilding-sofle-choc-on-android-8n1</guid>
      <description>&lt;p&gt;I built a custom Android keyboard. It got weird.&lt;/p&gt;

&lt;p&gt;A full IME - written in Kotlin, handling every keypress, suggestion, and cursor movement myself. The idea was to replicate the Sofle Choc split. Because.&lt;/p&gt;

&lt;p&gt;Here's what actually surprised me building it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backspace into a committed word is a whole thing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once a word is committed, the suggestion engine loses track of it. Backspacing into it looks like a new character to the IME. Getting autocomplete to pick up the fragment again meant wrapping three API calls in &lt;code&gt;beginBatchEdit&lt;/code&gt;/&lt;code&gt;endBatchEdit&lt;/code&gt; to stop the editor firing callbacks mid-operation. &lt;code&gt;AnySoftKeyboard&lt;/code&gt; does the same thing. Took me way too long to figure out why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next-word prediction is more interesting than I expected:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After every committed word, surface what probably comes next - T9 style, but without the hardware. Two layers: a static English seed (Norvig's bigram corpus, MIT licensed) and a personal layer that learns from your actual typing. The scoring uses temporal decay from &lt;code&gt;librime&lt;/code&gt; - recent use contributes a near-full boost, something you typed 1000 commits ago contributes almost nothing. Old habits fade. New ones take over within a few hundred keystrokes.&lt;/p&gt;

&lt;p&gt;Learned about bigrams, infigrams, suffix-array with binary search and a new shit called #beam_search from github.com/rime/librime&lt;/p&gt;

&lt;p&gt;Mixed language (Banglish, Hinglish) works without any language detection, kinda - the personal layer is just a string map. It doesn't care what language you're in. Again, ASK showed the way for MVP.&lt;/p&gt;

&lt;p&gt;The suggestion interface mirrors librime's Grammar pattern, context passes through the function call, not stored as shared mutable state. Took one refactor to get there but it cleaned up everything downstream.&lt;/p&gt;

&lt;p&gt;A seven part series on the same:&lt;br&gt;
&lt;a href="https://ikouchiha47.github.io/keyboard/" rel="noopener noreferrer"&gt;https://ikouchiha47.github.io/keyboard/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>android</category>
    </item>
  </channel>
</rss>
