markdown test file

This commit is contained in:
booleanmaybe 2026-04-02 20:49:44 -04:00
parent 6d65d07c10
commit edc347d258
9 changed files with 4778 additions and 0 deletions

763
testdata/go-concurrency.md vendored Normal file
View file

@ -0,0 +1,763 @@
# Go Concurrency
## Table of Contents
1. [Overview](#overview)
2. [Channels](#channels)
3. [Context](#context)
4. [Synchronization Primitives](#synchronization-primitives)
5. [Error Groups](#error-groups)
6. [Timers & Rate Limiting](#timers--rate-limiting)
7. [Race Conditions](#race-conditions)
8. [Concurrency Patterns](#concurrency-patterns)
 
---
 
## Overview
![Go Concurrency Model](svg/go-concurrency.svg)
Go's concurrency model is built on **CSP (Communicating Sequential Processes)**, a formal model where independent processes communicate through message passing rather than shared state. Instead of threads and locks, you work with goroutines and channels. The runtime multiplexes goroutines onto OS threads using the **GMP scheduler** (Goroutine, Machine, Processor), making goroutines extremely cheap — each starts with only ~2KB of stack space that grows and shrinks on demand.
> Don't communicate by sharing memory;
> share memory by communicating.
>
> — *Effective Go*
This principle inverts the traditional threading model. Rather than protecting shared data with mutexes, you pass data between goroutines through channels, ensuring only one goroutine accesses the data at a time by design.
 
### Goroutines
A goroutine is a lightweight concurrent function managed entirely by the Go runtime, not by the operating system. The `go` keyword launches one, and execution continues immediately in the calling goroutine without waiting for it to finish. Unlike OS threads (which typically consume 1-8MB of stack), goroutines are cheap enough to create by the thousands or even millions in a single process.
```go
func main() {
go doWork() // runs concurrently
// main goroutine continues here
time.Sleep(time.Second)
}
func doWork() {
fmt.Println("working")
}
```
Goroutines are cooperatively scheduled. They yield at:
- channel send/receive operations
- system calls (file I/O, network)
- function calls (including runtime checks)
- garbage collection safe points
- `runtime.Gosched()` explicit yield
Since Go 1.14, the scheduler can also preempt long-running goroutines at async safe points, preventing a single compute-heavy goroutine from starving others.
 
| Property | OS Thread | Goroutine |
|---|---|---|
| Initial stack | 18 MB | ~2 KB |
| Creation cost | ~1 ms | ~1 µs |
| Context switch | kernel-level | user-space |
| Practical limit | ~10K per process | ~1M+ per process |
| Managed by | OS scheduler | Go runtime (GMP) |
 
### WaitGroup
`sync.WaitGroup` coordinates goroutine completion when you don't need to pass results back through channels. The pattern has three steps:
1. `wg.Add(n)` — increment the counter *before* launching goroutines
2. `defer wg.Done()` — decrement when the goroutine finishes
3. `wg.Wait()` — block until the counter reaches zero
A common mistake is calling `Add` inside the goroutine rather than before launching it, which introduces a race condition.
```go
var wg sync.WaitGroup
for i := range 5 {
wg.Add(1)
go func() {
defer wg.Done()
process(i)
}()
}
wg.Wait() // blocks until all 5 complete
```
 
---
 
## Channels
![Go Channels — Plumbing Schematic](svg/channels.svg)
Channels are typed, goroutine-safe queues with blocking semantics. They serve as the primary mechanism for goroutine communication and synchronization — a channel both transfers data and coordinates timing between producer and consumer. Understanding the distinction between buffered and unbuffered channels is essential because they have fundamentally different synchronization behavior.
> A channel is a communication mechanism that lets one
> goroutine send values to another goroutine. Each channel
> is a conduit for values of a particular type.
>
> — *The Go Programming Language*, Donovan & Kernighan
 
### Unbuffered Channels
An unbuffered channel (`make(chan T)`) forces a synchronous handoff — the sender blocks until a receiver is ready, and vice versa. This provides a strong synchronization guarantee: at the moment a value is transferred, both goroutines are at a known point in their execution. Unbuffered channels are the default and should be your first choice unless you have a specific reason to buffer.
```go
ch := make(chan int)
go func() {
ch <- 42 // blocks until someone receives
}()
v := <-ch // blocks until someone sends
fmt.Println(v) // 42
```
&nbsp;
### Buffered Channels
Buffered channels (`make(chan T, N)`) decouple sender and receiver by providing N slots of internal storage. The sender only blocks when the buffer is full, and the receiver only blocks when the buffer is empty. This is useful when producers and consumers operate at different speeds, or when you want to limit concurrency by using the buffer capacity as a semaphore. Be careful with sizing — an oversized buffer hides backpressure problems, while an undersized one defeats the purpose.
```go
ch := make(chan int, 3) // buffer capacity of 3
ch <- 1 // doesn't block
ch <- 2 // doesn't block
ch <- 3 // doesn't block
ch <- 4 // blocks buffer full, waits for a receive
```
&nbsp;
| Behavior | Unbuffered (`make(chan T)`) | Buffered (`make(chan T, N)`) |
|---|---|---|
| Send blocks when | no receiver is waiting | buffer is full |
| Receive blocks when | no sender is waiting | buffer is empty |
| Synchronization | both goroutines meet | decoupled |
| Best for | signaling, handoffs | batching, rate smoothing |
&nbsp;
### Direction Constraints
Go lets you restrict channel access in function signatures to enforce intent at compile time. A `chan<-` parameter can only send; a `<-chan` parameter can only receive. This turns common mistakes — like accidentally closing a channel from the consumer side — into compile errors rather than runtime panics.
```go
func produce(out chan<- int) { ... } // send only
func consume(in <-chan int) { ... } // receive only
```
&nbsp;
### Select
The `select` statement multiplexes across multiple channel operations, enabling a goroutine to wait on several channels simultaneously. When multiple cases are ready, Go picks one at random to prevent starvation. The `default` case makes the select non-blocking — useful for polling or trylock patterns. A `select` with no cases (`select{}`) blocks forever, which is occasionally useful for keeping a main goroutine alive.
```go
select {
case v := <-ch1:
fmt.Println("from ch1:", v)
case v := <-ch2:
fmt.Println("from ch2:", v)
case ch3 <- value:
fmt.Println("sent to ch3")
default:
fmt.Println("no channel ready")
}
```
&nbsp;
### Close and Range
Closing a channel is a broadcast signal — all goroutines blocked on receive will unblock and get the zero value. The `range` keyword drains remaining buffered values and then exits when the channel closes.
Channel close rules:
- [x] only the sender should close
- [x] close after all values have been sent
- [ ] never close from the receiver side
- [ ] never send on a closed channel (panics)
- [ ] never close an already-closed channel (panics)
&nbsp;
```go
go func() {
for i := range 5 { ch <- i }
close(ch)
}()
for v := range ch { fmt.Println(v) } // 0, 1, 2, 3, 4
```
> You don't need to close a channel to release resources.
> Channels are garbage collected like any other value.
> Close a channel only when the receiver needs to know
> that no more values are coming.
&nbsp;
---
&nbsp;
## Context
![Go Context — Cancellation Tree](svg/context.svg)
`context.Context` is Go's mechanism for propagating cancellation signals, deadlines, and request-scoped values across API boundaries and goroutine trees. Every production Go service relies on it — HTTP servers create a context per request, database drivers respect context cancellation, and gRPC threads it through the entire call chain. Without context, you end up with goroutine leaks: workers that keep running long after the result is no longer needed.
> Context carries deadlines, cancellation signals,
> and request-scoped values across API boundaries.
> It is the standard way to control goroutine lifecycles
> in production Go code.
&nbsp;
### Cancellation
`context.WithCancel` returns a derived context and a cancel function. When cancel is called, the context's `Done()` channel closes, which unblocks any goroutine listening on it. The cancellation propagates downward through the context tree — cancelling a parent automatically cancels all derived child contexts. Always `defer cancel()` immediately after creation to prevent resource leaks, even if you plan to cancel explicitly later.
```go
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
select {
case <-ctx.Done(): return // cancelled
case result := <-doWork():
// use result
}
}()
```
&nbsp;
### Timeouts and Deadlines
`context.WithTimeout` cancels automatically after a duration, while `context.WithDeadline` cancels at a specific wall-clock time. Under the hood, `WithTimeout` is just `WithDeadline(parent, time.Now().Add(d))`. These are critical for networked services — without timeouts, a slow downstream dependency can cause cascading failures as goroutines pile up waiting indefinitely.
```go
// cancel after 3 seconds
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
result, err := fetchWithContext(ctx)
if errors.Is(err, context.DeadlineExceeded) {
log.Println("request timed out")
}
```
&nbsp;
| Function | Cancellation | Use case |
|---|---|---|
| `WithCancel(parent)` | manual `cancel()` call | user-initiated abort, shutdown |
| `WithTimeout(parent, d)` | after duration `d` | HTTP requests, RPC calls |
| `WithDeadline(parent, t)` | at time `t` | batch jobs with hard cutoff |
| `WithValue(parent, k, v)` | inherits parent's | trace IDs, auth tokens |
&nbsp;
### Context Tree
Contexts form a tree — cancelling a parent cancels all its children, but cancelling a child does not affect the parent or siblings. This mirrors the natural structure of request handling: a top-level HTTP handler creates a context, passes it to middleware, which passes it to service calls, which pass it to database queries. If the client disconnects, everything downstream gets cancelled automatically.
Context rules to live by:
- [x] pass as the **first parameter** of functions doing I/O or long-running work
- [x] always `defer cancel()` immediately after creation
- [x] use `context.TODO()` as a placeholder when unsure which context to use
- [ ] never store context in a struct field
- [ ] never pass `nil` — use `context.TODO()` instead
- [ ] never use `WithValue` for data that should be function parameters
&nbsp;
---
&nbsp;
## Synchronization Primitives
![Go sync — Locks, Gates & Pools](svg/sync-primitives.svg)
When channels aren't the right fit, the `sync` package provides low-level primitives. Reach for them when you need to:
- protect shared data structures from concurrent access
- run initialization code exactly once
- reuse expensive temporary objects across goroutines
- perform lock-free atomic updates on simple values
The general rule: prefer channels for coordination between goroutines, and use sync primitives for protecting shared data structures. In practice, most Go programs use both.
> Channels orchestrate; mutexes serialize.
> Use channels when ownership of data transfers
> between goroutines. Use mutexes when multiple
> goroutines access shared state in place.
&nbsp;
### Mutex and RWMutex
`sync.Mutex` provides mutual exclusion — only one goroutine can hold the lock at a time. `sync.RWMutex` is the read-write variant: it allows any number of concurrent readers, but writers get exclusive access. Use `RWMutex` when reads vastly outnumber writes (e.g., a configuration cache); use plain `Mutex` otherwise, since `RWMutex` has slightly higher overhead. Always use `defer mu.Unlock()` to guarantee release, even if a panic occurs.
```go
type SafeCounter struct {
mu sync.RWMutex
v map[string]int
}
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
defer c.mu.Unlock()
c.v[key]++
}
func (c *SafeCounter) Get(key string) int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.v[key]
}
```
&nbsp;
| Primitive | Concurrent reads | Concurrent writes | Use case |
|---|---|---|---|
| `sync.Mutex` | no | no | general shared state |
| `sync.RWMutex` | yes | no (exclusive) | read-heavy workloads |
| `sync.Map` | yes | yes (sharded) | dynamic key sets, few writes |
&nbsp;
### sync.Once
`sync.Once` guarantees that a function runs exactly once, no matter how many goroutines call it concurrently. This is the standard pattern for lazy initialization of singletons — database connections, configuration loading, expensive computations. All callers block until the first execution completes, so the initialized value is guaranteed visible. Go 1.21 added `sync.OnceValue` and `sync.OnceValues` for the common case where you need the return value.
```go
var (
instance *Database
once sync.Once
)
func GetDB() *Database {
once.Do(func() {
instance = connectToDatabase()
})
return instance
}
```
&nbsp;
### sync.Pool
`sync.Pool` provides a cache of temporary objects that can be reused across goroutines to reduce garbage collection pressure. Objects in the pool may be evicted at any GC cycle, so pools are only appropriate for short-lived, resettable objects. Good candidates:
- `bytes.Buffer` for serialization/encoding
- `json.Encoder` / `json.Decoder` instances
- scratch slices or structs in hot paths
- gzip/zlib writers for HTTP compression
In high-throughput servers, pools can dramatically reduce allocation rates and GC pause times.
```go
var bufPool = sync.Pool{
New: func() any { return new(bytes.Buffer) },
}
buf := bufPool.Get().(*bytes.Buffer)
defer func() { buf.Reset(); bufPool.Put(buf) }()
```
&nbsp;
### Atomic Operations
For simple counters, flags, and pointer swaps, `sync/atomic` provides lock-free operations that are faster than mutexes for single-value access. Go 1.19 introduced typed wrappers (`atomic.Int64`, `atomic.Bool`, `atomic.Pointer[T]`) that are safer and more readable than the older function-based API. Atomics are appropriate for metrics, counters, and feature flags — but if you need to update multiple related fields together, use a mutex instead.
```go
var ops atomic.Int64
for range 1000 {
go func() {
ops.Add(1)
}()
}
time.Sleep(time.Second)
fmt.Println("ops:", ops.Load()) // 1000
```
&nbsp;
---
&nbsp;
## Error Groups
![Go errgroup — Structured Concurrency](svg/errgroup.svg)
`golang.org/x/sync/errgroup` provides structured concurrency — a model where goroutines have a clear owner that launches them, waits for them, and handles their errors. What errgroup gives you over bare `go` + `sync.WaitGroup`:
- automatic error collection — `Wait()` returns the first non-nil error
- automatic cancellation — one failure cancels all siblings via derived context
- bounded concurrency — `SetLimit(n)` without manual semaphore channels
- guaranteed cleanup — no goroutine outlives the group
With errgroup, the first error cancels all sibling goroutines via the derived context, and `Wait()` returns that error to the caller.
> errgroup is Go's answer to structured concurrency.
> Every goroutine has an owner that waits for it
> and handles its errors — no fire-and-forget,
> no silent failures.
&nbsp;
### Basic Usage
The typical pattern is: create a group with `WithContext`, launch work with `g.Go`, and collect the result with `g.Wait`. The derived context is cancelled on the first error, so other goroutines can check `ctx.Done()` to bail out early. This naturally prevents goroutine leaks — you always know when all your goroutines have finished.
```go
import "golang.org/x/sync/errgroup"
func fetchAll(ctx context.Context, urls []string) error {
g, ctx := errgroup.WithContext(ctx)
for _, url := range urls {
g.Go(func() error {
return fetch(ctx, url)
})
}
return g.Wait() // returns first non-nil error
}
```
&nbsp;
### Bounded Concurrency
`SetLimit` caps the number of goroutines running concurrently. When the limit is reached, further calls to `g.Go` block until a running goroutine completes. This replaces the common manual semaphore-with-buffered-channel pattern, which is error-prone and verbose by comparison. Bounded concurrency is essential when you're making external calls (API requests, database queries) and need to avoid overwhelming downstream services.
```go
func processFiles(files []string) error {
g := new(errgroup.Group)
g.SetLimit(10) // at most 10 concurrent goroutines
for _, f := range files {
g.Go(func() error {
return processFile(f)
})
}
return g.Wait()
}
```
&nbsp;
| Feature | `sync.WaitGroup` | `errgroup.Group` |
|---|---|---|
| Error collection | manual | first error returned by `Wait()` |
| Context cancellation | manual | automatic on first error |
| Concurrency limiting | manual (buffered chan) | `SetLimit(n)` |
| Goroutine leak risk | higher | lower (structured ownership) |
&nbsp;
---
&nbsp;
## Timers & Rate Limiting
![Go Timers & Rate Limiting](svg/timers.svg)
Go's timer and ticker types integrate naturally with channels and `select`, making time-based operations composable with other concurrent primitives. Rate limiting is critical for production services — without it, a spike in traffic can overwhelm databases, exhaust file descriptors, or trigger cascading failures across microservices.
&nbsp;
### time.After and Timeouts
`time.After(d)` returns a `<-chan time.Time` that fires once after duration `d`. Combined with `select`, this creates a clean timeout pattern. Note that `time.After` allocates a timer that isn't garbage collected until it fires, so avoid it in tight loops — use `time.NewTimer` with explicit `Stop()` instead.
```go
select {
case result := <-ch:
fmt.Println("got:", result)
case <-time.After(3 * time.Second):
fmt.Println("timed out")
}
```
&nbsp;
### Ticker for Periodic Work
`time.NewTicker` fires at regular intervals and delivers ticks on its channel `C`. Unlike `time.After`, a ticker keeps firing until stopped — always `defer ticker.Stop()` to release resources. Tickers are the building block for polling loops, heartbeat checks, and periodic cleanup tasks. Pair them with `select` and `ctx.Done()` for clean shutdown.
```go
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ticker.C:
poll()
case <-ctx.Done():
return
}
}
```
&nbsp;
### Rate Limiting
The `golang.org/x/time/rate` package implements a token-bucket rate limiter. Tokens accumulate at a steady rate up to a burst capacity. `Wait(ctx)` blocks until a token is available or the context expires. This is the standard approach for rate-limiting outbound API calls, database queries, or any resource that can be overwhelmed. For simple cases, a `time.Ticker` can serve as a basic throttle — one request per tick.
```go
import "golang.org/x/time/rate"
// 10 events per second, burst of 30
limiter := rate.NewLimiter(10, 30)
func handleRequest(ctx context.Context) error {
if err := limiter.Wait(ctx); err != nil {
return err // context cancelled while waiting
}
return processRequest()
}
```
&nbsp;
| Method | Behavior | Use case |
|---|---|---|
| `limiter.Wait(ctx)` | blocks until allowed | background jobs, pipelines |
| `limiter.Allow()` | returns true/false immediately | request admission control |
| `limiter.Reserve()` | returns reservation with delay | scheduled future work |
&nbsp;
---
&nbsp;
## Race Conditions
![Go Race Conditions](svg/race-conditions.svg)
A data race occurs when two goroutines access the same variable concurrently and at least one access is a write. Data races are among the most insidious bugs in concurrent programs — they can produce corrupted data, crashes, or security vulnerabilities, and they often manifest only under specific timing conditions that are difficult to reproduce. Go takes data races seriously: the language specification states that a program with a data race has undefined behavior.
> A data race is not just a bug — it's undefined behavior.
> The compiler and runtime are allowed to assume your
> program is race-free. If it isn't, anything can happen:
> torn reads, stale caches, reordered writes.
&nbsp;
### The Race Detector
Go's built-in race detector is one of the language's most valuable tools. It instruments memory accesses at compile time and detects races at runtime, reporting the exact goroutines and stack traces involved. It catches races that would otherwise take weeks to reproduce in production.
Race detector checklist:
- [x] enable in CI with `go test -race ./...`
- [x] use during local development with `go run -race`
- [x] test concurrent code paths specifically (parallel subtests)
- [ ] don't run in production (~5-10x CPU and memory overhead)
- [ ] don't ignore race reports — they indicate undefined behavior
&nbsp;
```bash
go test -race ./...
go run -race main.go
go build -race -o myapp
```
&nbsp;
### Common Race Patterns
The most common race patterns in Go codebases:
1. **Unsynchronized map access** — maps are not goroutine-safe; concurrent read/write causes a runtime panic
2. **Loop variable capture** — pre-Go 1.22, goroutines in a loop all captured the same variable
3. **Slice append from multiple goroutines** — append is not atomic; the backing array can be corrupted
4. **Read-modify-write on shared variables**`counter++` is not atomic; use `sync/atomic` or a mutex
5. **Publishing partially initialized structs** — another goroutine may see incomplete fields
&nbsp;
**Unsynchronized map access** deserves special attention. The fix is either a `sync.Mutex` wrapping the map, or `sync.Map` for cases with many goroutines and infrequent writes.
```go
// BUG: concurrent map read/write
m := make(map[string]int)
go func() { m["a"] = 1 }()
go func() { _ = m["a"] }()
// FIX: use sync.Mutex or sync.Map
var mu sync.Mutex
go func() { mu.Lock(); m["a"] = 1; mu.Unlock() }()
go func() { mu.Lock(); _ = m["a"]; mu.Unlock() }()
```
&nbsp;
**Loop variable capture** was a persistent source of bugs before Go 1.22. Goroutines launched in a loop captured the loop variable by reference, meaning all of them would see the final value by the time they executed. Go 1.22 changed the semantics so each iteration gets its own copy. For pre-1.22 code, the fix is to pass the variable as a function argument.
&nbsp;
Concurrency code review checklist:
- [ ] every goroutine has a clear shutdown path (context, channel close, or WaitGroup)
- [ ] shared state is protected by a mutex or accessed through channels
- [ ] maps accessed from multiple goroutines use `sync.Mutex` or `sync.Map`
- [ ] `defer cancel()` follows every `WithCancel`/`WithTimeout`/`WithDeadline`
- [ ] `go test -race` passes in CI
- [ ] no goroutine leak — every `go func()` has an owner that waits for it
&nbsp;
---
&nbsp;
## Concurrency Patterns
![Go Concurrency Patterns](svg/patterns.svg)
These patterns are composable building blocks for concurrent Go programs. They compose naturally — a pipeline stage can internally use a worker pool, each worker can use an or-done wrapper, and the whole thing can be bounded with errgroup. Choosing the right pattern:
- **Fan-Out/Fan-In** — when you have N independent items to process in parallel
- **Pipeline** — when data flows through sequential transformation stages
- **Worker Pool** — when you need a fixed number of long-lived goroutines consuming from a queue
- **Or-Done** — when reading from a channel that may never close or may outlive your goroutine
> The strength of Go's concurrency model is composition.
> Goroutines and channels are cheap enough that you can
> use patterns as building blocks rather than designing
> monolithic concurrent architectures.
&nbsp;
### Fan-Out / Fan-In
Fan-out distributes work from a single source across multiple goroutines for parallel processing. Fan-in merges results from multiple goroutines back into a single channel. Together they form the most common parallelism pattern in Go — use fan-out when individual items are independent and CPU- or IO-bound, and fan-in when you need to collect results in one place. The number of fan-out workers should match the bottleneck: CPU count for compute work, or a reasonable concurrency limit for IO work.
```go
// fan-out: spawn N workers reading from the same input channel
for range workers {
go func() {
for job := range in { out <- process(job) }
}()
}
// fan-in: merge multiple channels into one
for _, ch := range channels {
go func() {
for v := range ch { merged <- v }
}()
}
```
&nbsp;
### Pipeline
A pipeline chains processing stages, each connected by channels. Each stage is a goroutine (or group of goroutines) that reads from an input channel, transforms the data, and writes to an output channel. Pipelines naturally handle backpressure — if a slow stage can't keep up, its input channel fills and the upstream stage blocks. This self-regulating behavior is one of the key advantages over thread-pool-based architectures.
```go
func stage(in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for v := range in { out <- transform(v) }
}()
return out
}
// compose: generate → square → print
out := square(generate(2, 3, 4))
for v := range out { fmt.Println(v) }
```
&nbsp;
### Worker Pool
A worker pool is a fixed set of goroutines consuming tasks from a shared job channel. It bounds concurrency naturally — the number of workers is the concurrency limit. This is simpler than errgroup's `SetLimit` when you don't need error collection, and it integrates well with context cancellation via `select`. Worker pools are the standard pattern for processing queues, handling connections, and parallelizing IO-bound work.
```go
for range numWorkers {
go func() {
for job := range jobs {
select {
case <-ctx.Done(): return
case results <- process(job):
}
}
}()
}
```
&nbsp;
### Or-Done Channel
The or-done pattern wraps a channel read so it respects context cancellation. Without it, a goroutine reading from a slow or abandoned channel will block indefinitely — a classic goroutine leak. The wrapper uses a nested `select` to check both the channel and the context's `Done()` channel on every iteration, ensuring the goroutine can always be shut down cleanly.
```go
func orDone(ctx context.Context, ch <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for {
select {
case <-ctx.Done():
return
case v, ok := <-ch:
if !ok { return }
select {
case out <- v:
case <-ctx.Done(): return
}
}
}
}()
return out
}
```
&nbsp;
| Pattern | Purpose | When to use |
|---|---|---|
| Fan-Out / Fan-In | parallelize independent work | CPU or IO bound items |
| Pipeline | chain sequential transformations | multi-stage data processing |
| Worker Pool | fixed concurrency with task queue | connection handling, job queues |
| Or-Done | cancellation-safe channel reads | wrapping external/untrusted channels |

705
testdata/svg/channels.svg vendored Normal file
View file

@ -0,0 +1,705 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 900">
<defs>
<!-- filters -->
<filter id="shadow" x="-6%" y="-6%" width="112%" height="116%">
<feDropShadow dx="1" dy="2" stdDeviation="5" flood-color="#000" flood-opacity="0.35"/>
</filter>
<filter id="title-glow" x="-10%" y="-10%" width="120%" height="120%">
<feGaussianBlur in="SourceAlpha" stdDeviation="8" result="blur"/>
<feFlood flood-color="#2dd4bf" flood-opacity="0.25" result="color"/>
<feComposite in="color" in2="blur" operator="in" result="glow"/>
<feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glow-blocked" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur"/>
<feFlood flood-color="#f472b6" flood-opacity="0.35" result="color"/>
<feComposite in="color" in2="blur" operator="in" result="glow"/>
<feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glow-warn" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur in="SourceAlpha" stdDeviation="3" result="blur"/>
<feFlood flood-color="#ec4899" flood-opacity="0.4" result="color"/>
<feComposite in="color" in2="blur" operator="in" result="glow"/>
<feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<!-- arrow markers -->
<marker id="arr-teal" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#2dd4bf"/>
</marker>
<marker id="arr-blue" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#60a5fa"/>
</marker>
<marker id="arr-amber" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#fbbf24"/>
</marker>
<marker id="arr-violet" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#a78bfa"/>
</marker>
<marker id="arr-rose" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#f472b6"/>
</marker>
<!-- data ball gradient -->
<radialGradient id="g-ball-teal" cx="0.4" cy="0.35" r="0.6">
<stop offset="0%" stop-color="#5eead4"/><stop offset="100%" stop-color="#0d9488"/>
</radialGradient>
<radialGradient id="g-ball-blue" cx="0.4" cy="0.35" r="0.6">
<stop offset="0%" stop-color="#93c5fd"/><stop offset="100%" stop-color="#2563eb"/>
</radialGradient>
<radialGradient id="g-ball-violet" cx="0.4" cy="0.35" r="0.6">
<stop offset="0%" stop-color="#c4b5fd"/><stop offset="100%" stop-color="#7c3aed"/>
</radialGradient>
<!-- pipe interior gradients (left-to-right flow feel) -->
<linearGradient id="gp-teal" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#14b8a6" stop-opacity="0.12"/><stop offset="100%" stop-color="#14b8a6" stop-opacity="0.04"/>
</linearGradient>
<linearGradient id="gp-blue" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#3b82f6" stop-opacity="0.12"/><stop offset="100%" stop-color="#3b82f6" stop-opacity="0.04"/>
</linearGradient>
<linearGradient id="gp-violet" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#8b5cf6" stop-opacity="0.12"/><stop offset="100%" stop-color="#8b5cf6" stop-opacity="0.04"/>
</linearGradient>
<linearGradient id="gp-rose" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#ec4899" stop-opacity="0.12"/><stop offset="100%" stop-color="#ec4899" stop-opacity="0.04"/>
</linearGradient>
<linearGradient id="gp-amber" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#f59e0b" stop-opacity="0.12"/><stop offset="100%" stop-color="#f59e0b" stop-opacity="0.04"/>
</linearGradient>
<!-- section separator gradients -->
<linearGradient id="g-sep-blue" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#60a5fa" stop-opacity="0"/><stop offset="15%" stop-color="#60a5fa" stop-opacity="0.3"/>
<stop offset="85%" stop-color="#60a5fa" stop-opacity="0.3"/><stop offset="100%" stop-color="#60a5fa" stop-opacity="0"/>
</linearGradient>
<linearGradient id="g-sep-violet" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#a78bfa" stop-opacity="0"/><stop offset="15%" stop-color="#a78bfa" stop-opacity="0.3"/>
<stop offset="85%" stop-color="#a78bfa" stop-opacity="0.3"/><stop offset="100%" stop-color="#a78bfa" stop-opacity="0"/>
</linearGradient>
<!-- ambient background glows -->
<radialGradient id="glow-top" cx="400" cy="200" r="340" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#14b8a6" stop-opacity="0.04"/><stop offset="100%" stop-color="#14b8a6" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-mid" cx="400" cy="460" r="340" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#3b82f6" stop-opacity="0.04"/><stop offset="100%" stop-color="#3b82f6" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-bot" cx="400" cy="700" r="340" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#8b5cf6" stop-opacity="0.03"/><stop offset="100%" stop-color="#8b5cf6" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-danger" cx="620" cy="700" r="240" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#ec4899" stop-opacity="0.03"/><stop offset="100%" stop-color="#ec4899" stop-opacity="0"/>
</radialGradient>
<!-- right panel -->
<linearGradient id="g-panel" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#141d2e" stop-opacity="0.9"/><stop offset="100%" stop-color="#0b1120" stop-opacity="0.95"/>
</linearGradient>
<!-- accent stripe -->
<linearGradient id="g-accent" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#14b8a6"/><stop offset="25%" stop-color="#3b82f6"/>
<stop offset="50%" stop-color="#f59e0b"/><stop offset="75%" stop-color="#8b5cf6"/>
<stop offset="100%" stop-color="#ec4899"/>
</linearGradient>
<!-- sync symbol -->
<symbol id="ico-sync" viewBox="0 0 20 12">
<path d="M2,6 L8,2 L8,10 Z" fill="none" stroke="currentColor" stroke-width="1.2"/>
<path d="M18,6 L12,2 L12,10 Z" fill="none" stroke="currentColor" stroke-width="1.2"/>
</symbol>
</defs>
<style>
.bg { fill: #0f172a; }
.title { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 28px; font-weight: 700; fill: #f1f5f9; }
.subtitle { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 13px; font-weight: 400; fill: #94a3b8; }
.section-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 600; letter-spacing: 1.5px; text-transform: uppercase; }
.pipe-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 13px; font-weight: 700; fill: #f1f5f9; }
.pipe-desc { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 400; fill: #94a3b8; }
.code { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 11px; fill: #e2e8f0; }
.code-sm { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 9px; fill: #e2e8f0; }
.g-label { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 10px; font-weight: 700; }
.lane-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10px; font-weight: 600; fill: #94a3b8; }
.legend-text { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; fill: #94a3b8; }
.mini-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 600; }
.quote { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 12px; font-weight: 600; fill: #f1f5f9; }
.quote-note { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; fill: #64748b; }
.warn-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10px; font-weight: 700; fill: #f472b6; }
.group-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 600; letter-spacing: 1.5px; text-transform: uppercase; }
</style>
<!-- background -->
<g opacity="0.05">
<pattern id="dots" width="40" height="40" patternUnits="userSpaceOnUse">
<circle cx="20" cy="20" r="0.8" fill="#94a3b8"/>
</pattern>
<rect width="1200" height="900" fill="url(#dots)"/>
</g>
<!-- ambient glows -->
<rect width="1200" height="900" fill="url(#glow-top)"/>
<rect width="1200" height="900" fill="url(#glow-mid)"/>
<rect width="1200" height="900" fill="url(#glow-bot)"/>
<rect width="1200" height="900" fill="url(#glow-danger)"/>
<!-- ================================================================ -->
<!-- TITLE -->
<!-- ================================================================ -->
<g filter="url(#title-glow)">
<text class="title" x="420" y="44" text-anchor="middle" dominant-baseline="central" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="28px" font-weight="700" fill="#f1f5f9">Go Channels — Plumbing Schematic</text>
</g>
<text class="subtitle" x="420" y="68" text-anchor="middle" dominant-baseline="central" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="400" fill="#94a3b8">types · operations · direction · patterns · gotchas</text>
<!-- ================================================================ -->
<!-- SECTION A: THE PIPE FORGE — Channel Types (y=88310) -->
<!-- ================================================================ -->
<text class="section-label" x="52" y="106" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">Channel Types</text>
<!-- ── Unbuffered ────────────────────────────────────────────────── -->
<g transform="translate(52, 124)">
<text class="pipe-label" x="0" y="0" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700">Unbuffered</text>
<!-- goroutine G1 (sender) -->
<g filter="url(#shadow)">
<circle cx="18" cy="36" r="14" fill="#134e4a" stroke="#14b8a6" stroke-width="1.5"/>
<text class="g-label" x="18" y="36" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" font-weight="700">G1</text>
</g>
<!-- arrow G1 → pipe -->
<path d="M34,36 L56,36" stroke="#2dd4bf" stroke-width="1.5" fill="none" marker-end="url(#arr-teal)"/>
<!-- pipe body (unbuffered — no slots, short) -->
<g filter="url(#shadow)">
<rect x="62" y="26" width="180" height="20" rx="10" fill="url(#gp-teal)" stroke="#14b8a6" stroke-width="2"/>
<!-- flow chevrons -->
<path d="M90,33 l4,3 l-4,3" stroke="#2dd4bf" stroke-width="0.8" fill="none" opacity="0.3"/>
<path d="M120,33 l4,3 l-4,3" stroke="#2dd4bf" stroke-width="0.8" fill="none" opacity="0.3"/>
<path d="M150,33 l4,3 l-4,3" stroke="#2dd4bf" stroke-width="0.8" fill="none" opacity="0.3"/>
<path d="M180,33 l4,3 l-4,3" stroke="#2dd4bf" stroke-width="0.8" fill="none" opacity="0.3"/>
<!-- data ball in transit -->
<circle cx="152" cy="36" r="5" fill="url(#g-ball-teal)"/>
</g>
<!-- sync symbol above pipe -->
<use href="#ico-sync" x="142" y="12" width="20" height="12" color="#2dd4bf" opacity="0.6"/>
<!-- arrow pipe → G2 -->
<path d="M244,36 L266,36" stroke="#2dd4bf" stroke-width="1.5" fill="none" marker-end="url(#arr-teal)"/>
<!-- goroutine G2 (receiver) -->
<g filter="url(#shadow)">
<circle cx="284" cy="36" r="14" fill="#134e4a" stroke="#14b8a6" stroke-width="1.5"/>
<text class="g-label" x="284" y="36" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" font-weight="700">G2</text>
</g>
<text class="pipe-desc" x="62" y="66" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#94a3b8">Synchronous handoff — both goroutines must rendezvous</text>
<text class="code" x="62" y="82" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="11px" fill="#e2e8f0">ch := make(chan int)</text>
</g>
<!-- ── Buffered ──────────────────────────────────────────────────── -->
<g transform="translate(52, 218)">
<text class="pipe-label" x="0" y="0" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700">Buffered</text>
<!-- goroutine sender -->
<g filter="url(#shadow)">
<circle cx="18" cy="36" r="14" fill="#134e4a" stroke="#14b8a6" stroke-width="1.5"/>
<text class="g-label" x="18" y="36" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" font-weight="700">G1</text>
</g>
<path d="M34,36 L56,36" stroke="#2dd4bf" stroke-width="1.5" fill="none" marker-end="url(#arr-teal)"/>
<!-- pipe body with 5 buffer slots -->
<g filter="url(#shadow)">
<rect x="62" y="26" width="360" height="20" rx="10" fill="url(#gp-teal)" stroke="#14b8a6" stroke-width="2"/>
<!-- slot dividers -->
<line x1="134" y1="28" x2="134" y2="44" stroke="#14b8a6" stroke-width="0.8" opacity="0.4"/>
<line x1="206" y1="28" x2="206" y2="44" stroke="#14b8a6" stroke-width="0.8" opacity="0.4"/>
<line x1="278" y1="28" x2="278" y2="44" stroke="#14b8a6" stroke-width="0.8" opacity="0.4"/>
<line x1="350" y1="28" x2="350" y2="44" stroke="#14b8a6" stroke-width="0.8" opacity="0.4"/>
<!-- filled slots (3 of 5) with data balls -->
<rect x="64" y="28" width="68" height="16" rx="4" fill="#14b8a6" opacity="0.15"/>
<circle cx="98" cy="36" r="5" fill="url(#g-ball-teal)"/>
<rect x="136" y="28" width="68" height="16" rx="4" fill="#14b8a6" opacity="0.15"/>
<circle cx="170" cy="36" r="5" fill="url(#g-ball-teal)"/>
<rect x="208" y="28" width="68" height="16" rx="4" fill="#14b8a6" opacity="0.15"/>
<circle cx="242" cy="36" r="5" fill="url(#g-ball-teal)"/>
<!-- empty slots (2 of 5) — just flow chevrons -->
<path d="M310,33 l4,3 l-4,3" stroke="#2dd4bf" stroke-width="0.8" fill="none" opacity="0.2"/>
<path d="M382,33 l4,3 l-4,3" stroke="#2dd4bf" stroke-width="0.8" fill="none" opacity="0.2"/>
</g>
<!-- capacity label -->
<text class="pipe-desc" x="200" y="18" text-anchor="middle" fill="#64748b" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400">3 / 5</text>
<!-- arrow pipe → receiver -->
<path d="M424,36 L446,36" stroke="#2dd4bf" stroke-width="1.5" fill="none" marker-end="url(#arr-teal)"/>
<!-- goroutine receiver -->
<g filter="url(#shadow)">
<circle cx="464" cy="36" r="14" fill="#134e4a" stroke="#14b8a6" stroke-width="1.5"/>
<text class="g-label" x="464" y="36" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" font-weight="700">G2</text>
</g>
<text class="pipe-desc" x="62" y="66" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#94a3b8">Async up to capacity — sender blocks only when buffer is full</text>
<text class="code" x="62" y="82" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="11px" fill="#e2e8f0">ch := make(chan int, 5)</text>
</g>
<!-- ── Nil Channel ───────────────────────────────────────────────── -->
<g transform="translate(52, 316)">
<text class="pipe-label" x="0" y="0" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700">Nil Channel</text>
<!-- blocked goroutine (sender) -->
<g filter="url(#glow-blocked)">
<circle cx="18" cy="36" r="14" fill="#3d0a24" stroke="#f472b6" stroke-width="1.5" stroke-dasharray="4 2"/>
<text class="g-label" x="18" y="36" text-anchor="middle" dominant-baseline="central" fill="#f472b6" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" font-weight="700">G1</text>
</g>
<!-- arrow into dead-end -->
<path d="M34,36 L56,36" stroke="#f472b6" stroke-width="1.2" fill="none" stroke-dasharray="4 3"/>
<!-- pipe body (nil — dashed, faded) -->
<rect x="62" y="26" width="180" height="20" rx="10" fill="none" stroke="#14b8a6" stroke-width="1.5" stroke-dasharray="6 3" opacity="0.35"/>
<!-- dead-end left wall -->
<g opacity="0.6">
<line x1="62" y1="24" x2="62" y2="48" stroke="#f472b6" stroke-width="2"/>
<line x1="58" y1="28" x2="66" y2="24" stroke="#f472b6" stroke-width="1"/>
<line x1="58" y1="34" x2="66" y2="30" stroke="#f472b6" stroke-width="1"/>
<line x1="58" y1="40" x2="66" y2="36" stroke="#f472b6" stroke-width="1"/>
<line x1="58" y1="46" x2="66" y2="42" stroke="#f472b6" stroke-width="1"/>
</g>
<!-- dead-end right wall -->
<g opacity="0.6">
<line x1="242" y1="24" x2="242" y2="48" stroke="#f472b6" stroke-width="2"/>
<line x1="238" y1="28" x2="246" y2="24" stroke="#f472b6" stroke-width="1"/>
<line x1="238" y1="34" x2="246" y2="30" stroke="#f472b6" stroke-width="1"/>
<line x1="238" y1="40" x2="246" y2="36" stroke="#f472b6" stroke-width="1"/>
<line x1="238" y1="46" x2="246" y2="42" stroke="#f472b6" stroke-width="1"/>
</g>
<!-- nil label -->
<text class="code" x="152" y="40" text-anchor="middle" dominant-baseline="central" fill="#f472b6" opacity="0.7" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="11px">nil</text>
<!-- blocked goroutine (receiver) -->
<g filter="url(#glow-blocked)">
<circle cx="284" cy="36" r="14" fill="#3d0a24" stroke="#f472b6" stroke-width="1.5" stroke-dasharray="4 2"/>
<text class="g-label" x="284" y="36" text-anchor="middle" dominant-baseline="central" fill="#f472b6" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" font-weight="700">G2</text>
</g>
<!-- dashed arrow from dead-end -->
<path d="M244,36 L266,36" stroke="#f472b6" stroke-width="1.2" fill="none" stroke-dasharray="4 3"/>
<text class="pipe-desc" x="62" y="66" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#94a3b8">Blocks forever on send and receive — useful to disable a select case</text>
<text class="code" x="62" y="82" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="11px" fill="#e2e8f0">var ch chan int // zero value = nil</text>
</g>
<!-- ================================================================ -->
<!-- SECTION SEPARATOR -->
<!-- ================================================================ -->
<rect x="52" y="418" width="780" height="1" fill="url(#g-sep-blue)"/>
<!-- ================================================================ -->
<!-- SECTION B: SWIM LANES — Operations (y=420600) -->
<!-- ================================================================ -->
<text class="section-label" x="52" y="442" fill="#60a5fa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">Operations &amp; Data Flow</text>
<!-- swim lane lines -->
<!-- Lane: producer x=140 -->
<line x1="140" y1="456" x2="140" y2="596" stroke="#334155" stroke-width="1" stroke-dasharray="4 2" opacity="0.5"/>
<text class="lane-label" x="140" y="454" text-anchor="middle" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="600" fill="#94a3b8">producer</text>
<!-- Lane: channel x=400 -->
<line x1="400" y1="456" x2="400" y2="596" stroke="#334155" stroke-width="1" stroke-dasharray="4 2" opacity="0.5"/>
<text class="lane-label" x="400" y="454" text-anchor="middle" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="600" fill="#94a3b8">channel</text>
<!-- Lane: consumer x=660 -->
<line x1="660" y1="456" x2="660" y2="596" stroke="#334155" stroke-width="1" stroke-dasharray="4 2" opacity="0.5"/>
<text class="lane-label" x="660" y="454" text-anchor="middle" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="600" fill="#94a3b8">consumer</text>
<!-- ── Send operation (y=468) ───────────────────────────────────── -->
<g transform="translate(0, 468)">
<!-- producer G-badge -->
<g filter="url(#shadow)">
<circle cx="140" cy="0" r="12" fill="#1e3a5f" stroke="#3b82f6" stroke-width="1.5"/>
<text class="g-label" x="140" y="0" text-anchor="middle" dominant-baseline="central" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" font-weight="700">G</text>
</g>
<!-- pipe bridge: producer → channel -->
<rect x="160" y="-8" width="220" height="16" rx="8" fill="url(#gp-blue)" stroke="#3b82f6" stroke-width="1.5"/>
<!-- flow chevrons -->
<path d="M200,-2 l4,2 l-4,2" stroke="#60a5fa" stroke-width="0.8" fill="none" opacity="0.4"/>
<path d="M240,-2 l4,2 l-4,2" stroke="#60a5fa" stroke-width="0.8" fill="none" opacity="0.4"/>
<path d="M280,-2 l4,2 l-4,2" stroke="#60a5fa" stroke-width="0.8" fill="none" opacity="0.4"/>
<path d="M320,-2 l4,2 l-4,2" stroke="#60a5fa" stroke-width="0.8" fill="none" opacity="0.4"/>
<!-- data ball moving right -->
<circle cx="310" cy="0" r="4.5" fill="url(#g-ball-blue)"/>
<!-- label -->
<text class="code" x="270" y="-16" text-anchor="middle" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="11px">ch &lt;- v</text>
<text class="pipe-desc" x="270" y="22" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#94a3b8">blocks if unbuffered or buffer full</text>
</g>
<!-- ── Receive operation (y=520) ────────────────────────────────── -->
<g transform="translate(0, 520)">
<!-- pipe bridge: channel → consumer -->
<rect x="420" y="-8" width="220" height="16" rx="8" fill="url(#gp-blue)" stroke="#3b82f6" stroke-width="1.5"/>
<!-- flow chevrons -->
<path d="M460,-2 l4,2 l-4,2" stroke="#60a5fa" stroke-width="0.8" fill="none" opacity="0.4"/>
<path d="M500,-2 l4,2 l-4,2" stroke="#60a5fa" stroke-width="0.8" fill="none" opacity="0.4"/>
<path d="M540,-2 l4,2 l-4,2" stroke="#60a5fa" stroke-width="0.8" fill="none" opacity="0.4"/>
<path d="M580,-2 l4,2 l-4,2" stroke="#60a5fa" stroke-width="0.8" fill="none" opacity="0.4"/>
<!-- data ball moving right -->
<circle cx="560" cy="0" r="4.5" fill="url(#g-ball-blue)"/>
<!-- consumer G-badge -->
<g filter="url(#shadow)">
<circle cx="660" cy="0" r="12" fill="#1e3a5f" stroke="#3b82f6" stroke-width="1.5"/>
<text class="g-label" x="660" y="0" text-anchor="middle" dominant-baseline="central" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" font-weight="700">G</text>
</g>
<!-- label -->
<text class="code" x="530" y="-16" text-anchor="middle" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="11px">v := &lt;-ch</text>
<text class="pipe-desc" x="530" y="22" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#94a3b8">blocks if channel is empty</text>
</g>
<!-- ── Close / Range (y=568) ────────────────────────────────────── -->
<g transform="translate(0, 570)">
<!-- channel pipe with valve -->
<rect x="370" y="-8" width="60" height="16" rx="8" fill="url(#gp-blue)" stroke="#3b82f6" stroke-width="1.5"/>
<!-- valve X symbol -->
<g transform="translate(400, 0)">
<line x1="-6" y1="-6" x2="6" y2="6" stroke="#f472b6" stroke-width="2" stroke-linecap="round"/>
<line x1="6" y1="-6" x2="-6" y2="6" stroke="#f472b6" stroke-width="2" stroke-linecap="round"/>
<circle cx="0" cy="0" r="9" fill="none" stroke="#f472b6" stroke-width="1.2" opacity="0.5"/>
</g>
<!-- drain trail: remaining values flowing out -->
<path d="M434,0 L520,0" stroke="#60a5fa" stroke-width="1.2" fill="none" stroke-dasharray="3 4"/>
<circle cx="450" cy="0" r="3" fill="url(#g-ball-blue)" opacity="0.7"/>
<circle cx="470" cy="0" r="3" fill="url(#g-ball-blue)" opacity="0.5"/>
<circle cx="490" cy="0" r="3" fill="url(#g-ball-blue)" opacity="0.3"/>
<!-- labels -->
<text class="code" x="340" y="4" text-anchor="end" fill="#f472b6" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="11px">close(ch)</text>
<text class="code" x="530" y="4" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="11px">for v := range ch</text>
<text class="pipe-desc" x="434" y="22" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#94a3b8">signals done — range drains remaining values</text>
</g>
<!-- ================================================================ -->
<!-- SECTION SEPARATOR -->
<!-- ================================================================ -->
<rect x="52" y="608" width="780" height="1" fill="url(#g-sep-violet)"/>
<!-- ================================================================ -->
<!-- SECTION C: PLUMBING YARD — Patterns & Gotchas (y=610780) -->
<!-- ================================================================ -->
<!-- ── LEFT: Patterns ────────────────────────────────────────────── -->
<text class="section-label" x="52" y="632" fill="#a78bfa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">Patterns</text>
<!-- Fan-Out: one pipe splits into 3 -->
<g transform="translate(52, 648)">
<text class="pipe-label" x="0" y="0" fill="#a78bfa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700">Fan-Out</text>
<text class="pipe-desc" x="80" y="0" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#94a3b8">one channel, N goroutines reading</text>
<!-- input pipe -->
<rect x="0" y="18" width="100" height="14" rx="7" fill="url(#gp-violet)" stroke="#8b5cf6" stroke-width="1.5"/>
<circle cx="40" cy="25" r="3.5" fill="url(#g-ball-violet)"/>
<!-- T-joint: bezier splits -->
<path d="M100,25 C120,25 125,12 145,12" stroke="#8b5cf6" stroke-width="1.5" fill="none"/>
<path d="M100,25 C120,25 125,25 145,25" stroke="#8b5cf6" stroke-width="1.5" fill="none"/>
<path d="M100,25 C120,25 125,38 145,38" stroke="#8b5cf6" stroke-width="1.5" fill="none"/>
<!-- output pipes to workers -->
<rect x="145" y="5" width="60" height="14" rx="7" fill="url(#gp-violet)" stroke="#8b5cf6" stroke-width="1.2"/>
<rect x="145" y="18" width="60" height="14" rx="7" fill="url(#gp-violet)" stroke="#8b5cf6" stroke-width="1.2"/>
<rect x="145" y="31" width="60" height="14" rx="7" fill="url(#gp-violet)" stroke="#8b5cf6" stroke-width="1.2"/>
<!-- worker G-badges -->
<circle cx="222" cy="12" r="9" fill="#271650" stroke="#8b5cf6" stroke-width="1.2"/>
<text class="g-label" x="222" y="12" text-anchor="middle" dominant-baseline="central" fill="#a78bfa" font-size="8" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-weight="700">W1</text>
<circle cx="222" cy="25" r="9" fill="#271650" stroke="#8b5cf6" stroke-width="1.2"/>
<text class="g-label" x="222" y="25" text-anchor="middle" dominant-baseline="central" fill="#a78bfa" font-size="8" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-weight="700">W2</text>
<circle cx="222" cy="38" r="9" fill="#271650" stroke="#8b5cf6" stroke-width="1.2"/>
<text class="g-label" x="222" y="38" text-anchor="middle" dominant-baseline="central" fill="#a78bfa" font-size="8" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-weight="700">W3</text>
</g>
<!-- Fan-In: 3 pipes merge into 1 -->
<g transform="translate(52, 710)">
<text class="pipe-label" x="0" y="0" fill="#a78bfa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700">Fan-In</text>
<text class="pipe-desc" x="65" y="0" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#94a3b8">N channels merged into single output</text>
<!-- source G-badges -->
<circle cx="10" cy="16" r="9" fill="#271650" stroke="#8b5cf6" stroke-width="1.2"/>
<text class="g-label" x="10" y="16" text-anchor="middle" dominant-baseline="central" fill="#a78bfa" font-size="8" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-weight="700">G1</text>
<circle cx="10" cy="29" r="9" fill="#271650" stroke="#8b5cf6" stroke-width="1.2"/>
<text class="g-label" x="10" y="29" text-anchor="middle" dominant-baseline="central" fill="#a78bfa" font-size="8" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-weight="700">G2</text>
<circle cx="10" cy="42" r="9" fill="#271650" stroke="#8b5cf6" stroke-width="1.2"/>
<text class="g-label" x="10" y="42" text-anchor="middle" dominant-baseline="central" fill="#a78bfa" font-size="8" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-weight="700">G3</text>
<!-- input pipes -->
<rect x="24" y="9" width="60" height="14" rx="7" fill="url(#gp-violet)" stroke="#8b5cf6" stroke-width="1.2"/>
<rect x="24" y="22" width="60" height="14" rx="7" fill="url(#gp-violet)" stroke="#8b5cf6" stroke-width="1.2"/>
<rect x="24" y="35" width="60" height="14" rx="7" fill="url(#gp-violet)" stroke="#8b5cf6" stroke-width="1.2"/>
<!-- Y-merge: bezier curves converging -->
<path d="M84,16 C104,16 109,29 129,29" stroke="#8b5cf6" stroke-width="1.5" fill="none"/>
<path d="M84,29 C104,29 109,29 129,29" stroke="#8b5cf6" stroke-width="1.5" fill="none"/>
<path d="M84,42 C104,42 109,29 129,29" stroke="#8b5cf6" stroke-width="1.5" fill="none"/>
<!-- output pipe -->
<rect x="129" y="22" width="100" height="14" rx="7" fill="url(#gp-violet)" stroke="#8b5cf6" stroke-width="1.5"/>
<circle cx="180" cy="29" r="3.5" fill="url(#g-ball-violet)"/>
</g>
<!-- Pipeline: chained stages -->
<g transform="translate(52, 776)">
<text class="pipe-label" x="0" y="0" fill="#a78bfa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700">Pipeline</text>
<text class="pipe-desc" x="72" y="0" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#94a3b8">chained stages connected by channels</text>
<!-- stage 1: gen -->
<rect x="0" y="14" width="70" height="28" rx="6" fill="#271650" stroke="#8b5cf6" stroke-width="1.2"/>
<text class="mini-label" x="35" y="28" text-anchor="middle" dominant-baseline="central" fill="#a78bfa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600">gen</text>
<!-- pipe ch1 -->
<rect x="76" y="21" width="50" height="14" rx="7" fill="url(#gp-violet)" stroke="#8b5cf6" stroke-width="1.2"/>
<circle cx="101" cy="28" r="3" fill="url(#g-ball-violet)"/>
<!-- stage 2: square -->
<rect x="132" y="14" width="70" height="28" rx="6" fill="#271650" stroke="#8b5cf6" stroke-width="1.2"/>
<text class="mini-label" x="167" y="28" text-anchor="middle" dominant-baseline="central" fill="#a78bfa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600">sq</text>
<!-- pipe ch2 -->
<rect x="208" y="21" width="50" height="14" rx="7" fill="url(#gp-violet)" stroke="#8b5cf6" stroke-width="1.2"/>
<circle cx="233" cy="28" r="3" fill="url(#g-ball-violet)"/>
<!-- stage 3: print -->
<rect x="264" y="14" width="70" height="28" rx="6" fill="#271650" stroke="#8b5cf6" stroke-width="1.2"/>
<text class="mini-label" x="299" y="28" text-anchor="middle" dominant-baseline="central" fill="#a78bfa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600">print</text>
<!-- direction labels -->
<text class="code-sm" x="101" y="14" text-anchor="middle" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">chan</text>
<text class="code-sm" x="233" y="14" text-anchor="middle" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">chan</text>
</g>
<!-- ── RIGHT: Gotchas ────────────────────────────────────────────── -->
<text class="section-label" x="440" y="632" fill="#f472b6" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">Gotchas</text>
<!-- Deadlock: cyclic pipes -->
<g transform="translate(440, 648)">
<text class="pipe-label" x="0" y="0" fill="#f472b6" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700">Deadlock</text>
<!-- two pipes forming a cycle -->
<g filter="url(#glow-warn)">
<!-- top pipe: left to right -->
<rect x="20" y="14" width="140" height="14" rx="7" fill="url(#gp-rose)" stroke="#ec4899" stroke-width="1.5"/>
<!-- bottom pipe: right to left -->
<rect x="20" y="48" width="140" height="14" rx="7" fill="url(#gp-rose)" stroke="#ec4899" stroke-width="1.5"/>
<!-- connecting curves (cycle) -->
<path d="M160,21 C180,21 180,55 160,55" stroke="#ec4899" stroke-width="1.5" fill="none"/>
<path d="M20,55 C0,55 0,21 20,21" stroke="#ec4899" stroke-width="1.5" fill="none"/>
</g>
<!-- blocked G-badges -->
<g filter="url(#glow-blocked)">
<circle cx="60" cy="28" r="8" fill="#3d0a24" stroke="#f472b6" stroke-width="1.2" stroke-dasharray="3 2"/>
<text class="g-label" x="60" y="28" text-anchor="middle" dominant-baseline="central" fill="#f472b6" font-size="7" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-weight="700">G1</text>
</g>
<g filter="url(#glow-blocked)">
<circle cx="120" cy="55" r="8" fill="#3d0a24" stroke="#f472b6" stroke-width="1.2" stroke-dasharray="3 2"/>
<text class="g-label" x="120" y="55" text-anchor="middle" dominant-baseline="central" fill="#f472b6" font-size="7" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-weight="700">G2</text>
</g>
<!-- circular arrow in center -->
<path d="M80,34 C95,34 95,48 80,48" stroke="#f472b6" stroke-width="1" fill="none" marker-end="url(#arr-rose)" opacity="0.6"/>
<text class="code-sm" x="20" y="80" fill="#f472b6" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">fatal error: all goroutines are asleep</text>
</g>
<!-- Goroutine Leak: pipe with no receiver, dripping -->
<g transform="translate(440, 748)">
<text class="pipe-label" x="0" y="0" fill="#f472b6" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700">Goroutine Leak</text>
<!-- stuck goroutine -->
<g filter="url(#glow-blocked)">
<circle cx="14" cy="24" r="10" fill="#3d0a24" stroke="#f472b6" stroke-width="1.2" stroke-dasharray="3 2"/>
<text class="g-label" x="14" y="24" text-anchor="middle" dominant-baseline="central" fill="#f472b6" font-size="8" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-weight="700">G</text>
</g>
<!-- pipe going nowhere -->
<rect x="30" y="17" width="120" height="14" rx="7" fill="url(#gp-rose)" stroke="#ec4899" stroke-width="1.5"/>
<circle cx="60" cy="24" r="3.5" fill="url(#g-ball-teal)" opacity="0.6"/>
<!-- drip drops at open end -->
<circle cx="156" cy="28" r="2" fill="#f472b6" opacity="0.6"/>
<circle cx="160" cy="38" r="1.5" fill="#f472b6" opacity="0.4"/>
<circle cx="157" cy="46" r="1" fill="#f472b6" opacity="0.25"/>
<text class="pipe-desc" x="0" y="58" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#94a3b8">blocked goroutine never freed — use context</text>
</g>
<!-- Send on Closed: explosion -->
<g transform="translate(440, 826)">
<text class="pipe-label" x="0" y="0" fill="#f472b6" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700">Send on Closed</text>
<!-- closed pipe with valve -->
<rect x="0" y="14" width="100" height="14" rx="7" fill="url(#gp-rose)" stroke="#ec4899" stroke-width="1.5"/>
<!-- valve X -->
<line x1="94" y1="14" x2="106" y2="28" stroke="#f472b6" stroke-width="2" stroke-linecap="round"/>
<line x1="106" y1="14" x2="94" y2="28" stroke="#f472b6" stroke-width="2" stroke-linecap="round"/>
<!-- data ball trying to enter → explosion burst -->
<g transform="translate(130, 21)">
<circle cx="0" cy="0" r="4" fill="url(#g-ball-teal)"/>
<!-- starburst -->
<path d="M-8,-8 L-3,-3 M8,-8 L3,-3 M-8,8 L-3,3 M8,8 L3,3 M0,-10 L0,-5 M0,10 L0,5 M-10,0 L-5,0 M10,0 L5,0" stroke="#f472b6" stroke-width="1.5" stroke-linecap="round" opacity="0.8"/>
</g>
<text class="code-sm" x="0" y="44" fill="#f472b6" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">panic: send on closed channel</text>
<text class="pipe-desc" x="0" y="58" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#94a3b8">only the sender should close — never the receiver</text>
</g>
<!-- ================================================================ -->
<!-- RIGHT PANEL -->
<!-- ================================================================ -->
<g transform="translate(860, 88)">
<rect width="310" height="780" rx="14" fill="url(#g-panel)" stroke="#334155" stroke-width="1"/>
<!-- ── Visual Key ──────────────────────────────────────────────── -->
<text class="group-label" x="24" y="28" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">Visual Key</text>
<g transform="translate(24, 46)">
<!-- solid pipe = active channel -->
<rect x="0" y="0" width="40" height="10" rx="5" fill="url(#gp-teal)" stroke="#14b8a6" stroke-width="1.2"/>
<text class="legend-text" x="52" y="9" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#94a3b8">Active channel</text>
<!-- dashed pipe = nil channel -->
<rect x="0" y="22" width="40" height="10" rx="5" fill="none" stroke="#14b8a6" stroke-width="1.2" stroke-dasharray="4 2" opacity="0.4"/>
<text class="legend-text" x="52" y="31" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#94a3b8">Nil channel</text>
<!-- pipe with slots = buffered -->
<rect x="0" y="44" width="40" height="10" rx="5" fill="url(#gp-teal)" stroke="#14b8a6" stroke-width="1.2"/>
<line x1="14" y1="45" x2="14" y2="53" stroke="#14b8a6" stroke-width="0.6" opacity="0.5"/>
<line x1="27" y1="45" x2="27" y2="53" stroke="#14b8a6" stroke-width="0.6" opacity="0.5"/>
<text class="legend-text" x="52" y="53" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#94a3b8">Buffered (with slots)</text>
<!-- data ball -->
<circle cx="20" cy="71" r="4" fill="url(#g-ball-teal)"/>
<text class="legend-text" x="52" y="75" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#94a3b8">Data value in transit</text>
<!-- G-badge -->
<circle cx="20" cy="93" r="8" fill="#134e4a" stroke="#14b8a6" stroke-width="1"/>
<text class="g-label" x="20" y="93" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-size="8" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-weight="700">G</text>
<text class="legend-text" x="52" y="97" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#94a3b8">Goroutine</text>
<!-- blocked G -->
<circle cx="20" cy="115" r="8" fill="#3d0a24" stroke="#f472b6" stroke-width="1" stroke-dasharray="3 2"/>
<text class="g-label" x="20" y="115" text-anchor="middle" dominant-baseline="central" fill="#f472b6" font-size="8" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-weight="700">G</text>
<text class="legend-text" x="52" y="119" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#94a3b8">Blocked goroutine</text>
<!-- valve X -->
<g transform="translate(20, 139)">
<line x1="-5" y1="-5" x2="5" y2="5" stroke="#f472b6" stroke-width="1.5" stroke-linecap="round"/>
<line x1="5" y1="-5" x2="-5" y2="5" stroke="#f472b6" stroke-width="1.5" stroke-linecap="round"/>
</g>
<text class="legend-text" x="52" y="143" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#94a3b8">close(ch) valve</text>
<!-- warning triangle -->
<path d="M20,153 L14,163 L26,163 Z" fill="none" stroke="#f472b6" stroke-width="1.2"/>
<text class="legend-text" x="52" y="163" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#94a3b8">Runtime error / trap</text>
</g>
<!-- ── Direction Constraints ───────────────────────────────────── -->
<text class="group-label" x="24" y="234" fill="#fbbf24" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">Direction Constraints</text>
<g transform="translate(24, 252)">
<!-- chan T (bidirectional) -->
<rect x="0" y="0" width="100" height="14" rx="7" fill="url(#gp-amber)" stroke="#f59e0b" stroke-width="1.2"/>
<!-- bidirectional chevrons -->
<path d="M20,4 l4,3 l-4,3" stroke="#fbbf24" stroke-width="0.8" fill="none" opacity="0.5"/>
<path d="M80,10 l-4,-3 l4,-3" stroke="#fbbf24" stroke-width="0.8" fill="none" opacity="0.5"/>
<text class="code" x="116" y="11" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="11px">chan T</text>
<text class="pipe-desc" x="170" y="11" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#94a3b8">send &amp; receive</text>
<!-- chan<- T (send-only) -->
<rect x="0" y="28" width="100" height="14" rx="7" fill="url(#gp-amber)" stroke="#f59e0b" stroke-width="1.2"/>
<path d="M20,32 l4,3 l-4,3" stroke="#fbbf24" stroke-width="0.8" fill="none" opacity="0.5"/>
<path d="M50,32 l4,3 l-4,3" stroke="#fbbf24" stroke-width="0.8" fill="none" opacity="0.5"/>
<path d="M80,32 l4,3 l-4,3" stroke="#fbbf24" stroke-width="0.8" fill="none" opacity="0.5"/>
<!-- blocked right end -->
<line x1="100" y1="28" x2="100" y2="42" stroke="#f59e0b" stroke-width="2.5"/>
<text class="code" x="116" y="39" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="11px">chan&lt;- T</text>
<text class="pipe-desc" x="170" y="39" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#94a3b8">send only</text>
<!-- <-chan T (receive-only) -->
<rect x="0" y="56" width="100" height="14" rx="7" fill="url(#gp-amber)" stroke="#f59e0b" stroke-width="1.2"/>
<path d="M80,66 l-4,-3 l4,-3" stroke="#fbbf24" stroke-width="0.8" fill="none" opacity="0.5"/>
<path d="M50,66 l-4,-3 l4,-3" stroke="#fbbf24" stroke-width="0.8" fill="none" opacity="0.5"/>
<path d="M20,66 l-4,-3 l4,-3" stroke="#fbbf24" stroke-width="0.8" fill="none" opacity="0.5"/>
<!-- blocked left end -->
<line x1="0" y1="56" x2="0" y2="70" stroke="#f59e0b" stroke-width="2.5"/>
<text class="code" x="116" y="67" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="11px">&lt;-chan T</text>
<text class="pipe-desc" x="170" y="67" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#94a3b8">recv only</text>
<text class="quote-note" x="0" y="92" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">Enforced at compile time.</text>
<text class="quote-note" x="0" y="107" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">Use in function signatures to</text>
<text class="quote-note" x="0" y="122" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">restrict channel access intent.</text>
</g>
<!-- ── Select Multiplexer ──────────────────────────────────────── -->
<text class="group-label" x="24" y="402" fill="#60a5fa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">Select Multiplexer</text>
<g transform="translate(24, 418)">
<!-- three pipes converging into a select block -->
<rect x="0" y="0" width="70" height="12" rx="6" fill="url(#gp-blue)" stroke="#3b82f6" stroke-width="1"/>
<rect x="0" y="18" width="70" height="12" rx="6" fill="url(#gp-blue)" stroke="#3b82f6" stroke-width="1"/>
<rect x="0" y="36" width="70" height="12" rx="6" fill="url(#gp-blue)" stroke="#3b82f6" stroke-width="1"/>
<!-- labels on pipes -->
<text class="code-sm" x="35" y="9" text-anchor="middle" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">ch1</text>
<text class="code-sm" x="35" y="27" text-anchor="middle" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">ch2</text>
<text class="code-sm" x="35" y="45" text-anchor="middle" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">ch3</text>
<!-- converge arrows -->
<path d="M70,6 C85,6 90,24 100,24" stroke="#3b82f6" stroke-width="1.2" fill="none"/>
<path d="M70,24 C85,24 90,24 100,24" stroke="#3b82f6" stroke-width="1.2" fill="none"/>
<path d="M70,42 C85,42 90,24 100,24" stroke="#3b82f6" stroke-width="1.2" fill="none"/>
<!-- select block -->
<rect x="100" y="12" width="64" height="28" rx="6" fill="#1e3a5f" stroke="#3b82f6" stroke-width="1.2"/>
<text class="mini-label" x="132" y="26" text-anchor="middle" dominant-baseline="central" fill="#60a5fa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600">select</text>
<!-- output -->
<path d="M164,26 L190,26" stroke="#3b82f6" stroke-width="1.2" fill="none" marker-end="url(#arr-blue)"/>
<text class="quote-note" x="0" y="66" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">Multiplexes across channels.</text>
<text class="quote-note" x="0" y="81" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">First ready case wins.</text>
<text class="quote-note" x="0" y="96" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">Use default for non-blocking.</text>
</g>
<!-- ── Key Insight ─────────────────────────────────────────────── -->
<text class="group-label" x="24" y="542" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">Key Insight</text>
<g transform="translate(24, 558)">
<rect x="-4" y="0" width="270" height="58" rx="8" fill="none" stroke="#334155" stroke-width="0.5"/>
<rect x="-4" y="0" width="4" height="58" rx="2" fill="url(#g-accent)"/>
<text class="quote" x="14" y="20" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="12px" font-weight="600" fill="#f1f5f9">Channels are goroutine-safe</text>
<text class="quote" x="14" y="38" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="12px" font-weight="600" fill="#f1f5f9">queues with blocking semantics.</text>
<text class="quote-note" x="0" y="80" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">Unbuffered = synchronization point</text>
<text class="quote-note" x="0" y="97" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">Buffered = decoupled producer/consumer</text>
<text class="quote-note" x="0" y="114" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">Nil = disabled select case</text>
<text class="quote-note" x="0" y="131" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">Closed = broadcast "done" signal</text>
<text class="quote-note" x="0" y="160" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">The sender should close the channel,</text>
<text class="quote-note" x="0" y="177" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">never the receiver — closing is a</text>
<text class="quote-note" x="0" y="194" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">broadcast signal, not cleanup.</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 51 KiB

384
testdata/svg/context.svg vendored Normal file
View file

@ -0,0 +1,384 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 700">
<defs>
<!-- filters -->
<filter id="shadow" x="-6%" y="-6%" width="112%" height="116%">
<feDropShadow dx="1" dy="2" stdDeviation="5" flood-color="#000" flood-opacity="0.35"/>
</filter>
<filter id="title-glow" x="-10%" y="-10%" width="120%" height="120%">
<feGaussianBlur in="SourceAlpha" stdDeviation="8" result="blur"/>
<feFlood flood-color="#60a5fa" flood-opacity="0.25" result="color"/>
<feComposite in="color" in2="blur" operator="in" result="glow"/>
<feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<!-- ambient background glows -->
<radialGradient id="glow-top" cx="400" cy="200" r="340" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#3b82f6" stop-opacity="0.04"/><stop offset="100%" stop-color="#3b82f6" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-mid" cx="350" cy="400" r="340" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#14b8a6" stop-opacity="0.04"/><stop offset="100%" stop-color="#14b8a6" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-bot" cx="400" cy="600" r="300" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#f59e0b" stop-opacity="0.03"/><stop offset="100%" stop-color="#f59e0b" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-cancel" cx="650" cy="300" r="200" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#ec4899" stop-opacity="0.03"/><stop offset="100%" stop-color="#ec4899" stop-opacity="0"/>
</radialGradient>
<!-- right panel -->
<linearGradient id="g-panel" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#141d2e" stop-opacity="0.9"/><stop offset="100%" stop-color="#0b1120" stop-opacity="0.95"/>
</linearGradient>
<!-- accent stripe -->
<linearGradient id="g-accent" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#3b82f6"/><stop offset="25%" stop-color="#14b8a6"/>
<stop offset="50%" stop-color="#f59e0b"/><stop offset="75%" stop-color="#ec4899"/>
<stop offset="100%" stop-color="#14b8a6"/>
</linearGradient>
<!-- edge gradients -->
<linearGradient id="edge-blue-blue" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#60a5fa"/><stop offset="100%" stop-color="#60a5fa"/>
</linearGradient>
<linearGradient id="edge-blue-amber" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#60a5fa"/><stop offset="100%" stop-color="#fbbf24"/>
</linearGradient>
<linearGradient id="edge-blue-teal" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#60a5fa"/><stop offset="100%" stop-color="#2dd4bf"/>
</linearGradient>
<linearGradient id="edge-amber-teal" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#fbbf24"/><stop offset="100%" stop-color="#2dd4bf"/>
</linearGradient>
<!-- timeline pipe gradient -->
<linearGradient id="gp-timeline" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#3b82f6" stop-opacity="0.12"/><stop offset="100%" stop-color="#3b82f6" stop-opacity="0.04"/>
</linearGradient>
<linearGradient id="gp-done" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#14b8a6" stop-opacity="0.15"/><stop offset="100%" stop-color="#14b8a6" stop-opacity="0.04"/>
</linearGradient>
<!-- section separator -->
<linearGradient id="g-sep-amber" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#fbbf24" stop-opacity="0"/><stop offset="15%" stop-color="#fbbf24" stop-opacity="0.3"/>
<stop offset="85%" stop-color="#fbbf24" stop-opacity="0.3"/><stop offset="100%" stop-color="#fbbf24" stop-opacity="0"/>
</linearGradient>
</defs>
<style>
.bg { fill: #0f172a; }
.title { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 28px; font-weight: 700; fill: #f1f5f9; }
.subtitle { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 13px; font-weight: 400; fill: #94a3b8; }
.section-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 600; letter-spacing: 1.5px; text-transform: uppercase; }
.node-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 700; fill: #f1f5f9; }
.node-label-sm { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 9.5px; font-weight: 700; fill: #f1f5f9; }
.code { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 11px; fill: #e2e8f0; }
.code-sm { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 9.5px; fill: #e2e8f0; }
.desc { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; fill: #cbd5e1; }
.label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; fill: #94a3b8; }
.note { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; fill: #64748b; }
.group-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 600; letter-spacing: 1.5px; text-transform: uppercase; }
.quote { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 12px; font-weight: 600; fill: #f1f5f9; }
.quote-note { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; fill: #64748b; }
.timeline-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10px; font-weight: 600; }
.mini-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10px; font-weight: 600; }
</style>
<!-- background -->
<g opacity="0.05">
<pattern id="dots" width="40" height="40" patternUnits="userSpaceOnUse">
<circle cx="20" cy="20" r="0.8" fill="#94a3b8"/>
</pattern>
<rect width="1200" height="700" fill="url(#dots)"/>
</g>
<!-- ambient glows -->
<rect width="1200" height="700" fill="url(#glow-top)"/>
<rect width="1200" height="700" fill="url(#glow-mid)"/>
<rect width="1200" height="700" fill="url(#glow-bot)"/>
<rect width="1200" height="700" fill="url(#glow-cancel)"/>
<!-- ================================================================ -->
<!-- TITLE -->
<!-- ================================================================ -->
<g filter="url(#title-glow)">
<text class="title" x="430" y="40" text-anchor="middle" dominant-baseline="central" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="28px" font-weight="700" fill="#f1f5f9">Go Context — Cancellation Tree</text>
</g>
<text class="subtitle" x="430" y="66" text-anchor="middle" dominant-baseline="central" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="400" fill="#94a3b8">propagation · cancellation · timeouts · deadlines</text>
<!-- ================================================================ -->
<!-- THE CONTEXT TREE -->
<!-- ================================================================ -->
<!-- ── Tree Edges (drawn first, behind nodes) ────────────────────── -->
<!-- Background() → WithCancel -->
<path d="M350,170 C350,192 200,192 200,214" stroke="url(#edge-blue-blue)" stroke-width="1.5" fill="none"/>
<!-- Background() → WithTimeout -->
<path d="M350,170 C350,192 350,192 350,214" stroke="url(#edge-blue-amber)" stroke-width="1.5" fill="none"/>
<!-- Background() → WithDeadline -->
<path d="M350,170 C350,192 510,192 510,214" stroke="url(#edge-blue-amber)" stroke-width="1.5" fill="none"/>
<!-- WithCancel → worker-A -->
<path d="M200,286 C200,310 130,310 130,334" stroke="url(#edge-blue-teal)" stroke-width="1.5" fill="none"/>
<!-- WithCancel → worker-B -->
<path d="M200,286 C200,310 270,310 270,334" stroke="url(#edge-blue-teal)" stroke-width="1.5" fill="none"/>
<!-- WithTimeout → httpHandler -->
<path d="M350,286 C350,310 350,310 350,334" stroke="url(#edge-amber-teal)" stroke-width="1.5" fill="none"/>
<!-- ── Tree Nodes ────────────────────────────────────────────────── -->
<!-- Root: Background() -->
<g filter="url(#shadow)">
<circle cx="350" cy="130" r="40" fill="none" stroke="#3b82f6" stroke-width="1.5"/>
<text class="node-label" x="350" y="126" text-anchor="middle" dominant-baseline="central" fill="#60a5fa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="700">Background()</text>
<text class="code-sm" x="350" y="142" text-anchor="middle" fill="#94a3b8" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9.5px">root</text>
</g>
<!-- WithCancel -->
<g filter="url(#shadow)">
<circle cx="200" cy="250" r="36" fill="none" stroke="#3b82f6" stroke-width="1.5"/>
<text class="node-label" x="200" y="246" text-anchor="middle" dominant-baseline="central" fill="#60a5fa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="700">WithCancel</text>
<text class="code-sm" x="200" y="262" text-anchor="middle" fill="#94a3b8" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9.5px">cancel()</text>
</g>
<!-- WithTimeout -->
<g filter="url(#shadow)">
<circle cx="350" cy="250" r="36" fill="none" stroke="#f59e0b" stroke-width="1.5"/>
<text class="node-label" x="350" y="246" text-anchor="middle" dominant-baseline="central" fill="#fbbf24" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="700">WithTimeout</text>
<text class="code-sm" x="350" y="262" text-anchor="middle" fill="#94a3b8" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9.5px">5s</text>
</g>
<!-- WithDeadline -->
<g filter="url(#shadow)">
<circle cx="510" cy="250" r="36" fill="none" stroke="#f59e0b" stroke-width="1.5"/>
<text class="node-label" x="510" y="246" text-anchor="middle" dominant-baseline="central" fill="#fbbf24" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="700">WithDeadline</text>
<text class="code-sm" x="510" y="262" text-anchor="middle" fill="#94a3b8" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9.5px">time.Time</text>
</g>
<!-- Leaf: worker-A -->
<g filter="url(#shadow)">
<circle cx="130" cy="356" r="22" fill="none" stroke="#14b8a6" stroke-width="1.5"/>
<text class="node-label-sm" x="130" y="356" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9.5px" font-weight="700">worker-A</text>
</g>
<!-- Leaf: worker-B -->
<g filter="url(#shadow)">
<circle cx="270" cy="356" r="22" fill="none" stroke="#14b8a6" stroke-width="1.5"/>
<text class="node-label-sm" x="270" y="356" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9.5px" font-weight="700">worker-B</text>
</g>
<!-- Leaf: httpHandler -->
<g filter="url(#shadow)">
<circle cx="350" cy="356" r="22" fill="none" stroke="#14b8a6" stroke-width="1.5"/>
<text class="node-label-sm" x="350" y="356" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9.5px" font-weight="700">httpReq</text>
</g>
<!-- ================================================================ -->
<!-- CANCELLATION CASCADE (right side of tree) -->
<!-- ================================================================ -->
<g transform="translate(640, 160)">
<text class="section-label" x="0" y="0" fill="#f472b6" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">Cancellation Cascade</text>
<!-- scenario: cancel the WithCancel parent -->
<!-- the "cancel()" call marker -->
<g transform="translate(30, 30)">
<!-- X marker on the cancel call -->
<circle cx="0" cy="0" r="12" fill="none" stroke="#ec4899" stroke-width="1.5"/>
<line x1="-6" y1="-6" x2="6" y2="6" stroke="#ec4899" stroke-width="2" stroke-linecap="round"/>
<line x1="6" y1="-6" x2="-6" y2="6" stroke="#ec4899" stroke-width="2" stroke-linecap="round"/>
<text class="code-sm" x="20" y="4" fill="#f472b6" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9.5px">cancel()</text>
</g>
<!-- dashed rose propagation lines -->
<path d="M30,46 C30,60 -20,70 -20,90" stroke="#ec4899" stroke-width="1.2" fill="none" stroke-dasharray="4 3"/>
<path d="M30,46 C30,60 80,70 80,90" stroke="#ec4899" stroke-width="1.2" fill="none" stroke-dasharray="4 3"/>
<!-- cancelled child nodes (miniature) -->
<g transform="translate(-20, 104)">
<circle cx="0" cy="0" r="14" fill="none" stroke="#ec4899" stroke-width="1.2" stroke-dasharray="4 2"/>
<text class="node-label-sm" x="0" y="0" text-anchor="middle" dominant-baseline="central" fill="#f472b6" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9.5px" font-weight="700">w-A</text>
</g>
<g transform="translate(80, 104)">
<circle cx="0" cy="0" r="14" fill="none" stroke="#ec4899" stroke-width="1.2" stroke-dasharray="4 2"/>
<text class="node-label-sm" x="0" y="0" text-anchor="middle" dominant-baseline="central" fill="#f472b6" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9.5px" font-weight="700">w-B</text>
</g>
<!-- annotation -->
<text class="desc" x="-10" y="140" font-size="10" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" fill="#cbd5e1">cancel propagates to</text>
<text class="desc" x="-10" y="154" font-size="10" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" fill="#cbd5e1">all children</text>
<!-- arrow showing downward propagation -->
<path d="M30,56 L30,70" stroke="#ec4899" stroke-width="1" fill="none" marker-end="url(#arr-rose)"/>
</g>
<!-- arrow marker for rose -->
<defs>
<marker id="arr-rose" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="10" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#ec4899"/>
</marker>
<marker id="arr-teal" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="10" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#2dd4bf"/>
</marker>
<marker id="arr-amber" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="10" markerHeight="7" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#fbbf24"/>
</marker>
</defs>
<!-- ================================================================ -->
<!-- TIMELINE STRIP (y=430650) -->
<!-- ================================================================ -->
<rect x="32" y="420" width="800" height="1" fill="url(#g-sep-amber)"/>
<text class="section-label" x="52" y="448" fill="#fbbf24" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">Context Lifecycle</text>
<g transform="translate(52, 470)">
<!-- main timeline bar -->
<rect x="0" y="20" width="760" height="16" rx="8" fill="url(#gp-timeline)" stroke="#334155" stroke-width="1"/>
<!-- phase 1: created -->
<rect x="2" y="22" width="140" height="12" rx="6" fill="#3b82f6" opacity="0.15"/>
<text class="timeline-label" x="72" y="30" text-anchor="middle" fill="#60a5fa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="600">created</text>
<!-- phase 2: active -->
<rect x="148" y="22" width="340" height="12" rx="6" fill="#14b8a6" opacity="0.12"/>
<text class="timeline-label" x="318" y="30" text-anchor="middle" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="600">active — Done() blocks</text>
<!-- phase 3: cancelled -->
<rect x="494" y="22" width="260" height="12" rx="6" fill="#ec4899" opacity="0.12"/>
<text class="timeline-label" x="624" y="30" text-anchor="middle" fill="#f472b6" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="600">cancelled — Done() closes</text>
<!-- phase dividers -->
<line x1="145" y1="18" x2="145" y2="40" stroke="#64748b" stroke-width="0.8" stroke-dasharray="3 2"/>
<line x1="491" y1="18" x2="491" y2="40" stroke="#64748b" stroke-width="0.8" stroke-dasharray="3 2"/>
<!-- Done() channel pipe -->
<g transform="translate(0, 54)">
<text class="code-sm" x="0" y="10" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9.5px">ctx.Done()</text>
<!-- pipe representing the Done channel (open, blocking) -->
<rect x="90" y="2" width="200" height="14" rx="7" fill="url(#gp-done)" stroke="#14b8a6" stroke-width="1.2"/>
<!-- chevrons showing blocked state -->
<path d="M130,6 l3,3 l-3,3" stroke="#2dd4bf" stroke-width="0.7" fill="none" opacity="0.3"/>
<path d="M160,6 l3,3 l-3,3" stroke="#2dd4bf" stroke-width="0.7" fill="none" opacity="0.3"/>
<path d="M190,6 l3,3 l-3,3" stroke="#2dd4bf" stroke-width="0.7" fill="none" opacity="0.3"/>
<path d="M220,6 l3,3 l-3,3" stroke="#2dd4bf" stroke-width="0.7" fill="none" opacity="0.3"/>
<text class="label" x="145" y="-2" font-size="9" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" fill="#94a3b8">blocks (open chan)</text>
<!-- X valve: channel closes -->
<g transform="translate(300, 9)">
<line x1="-5" y1="-5" x2="5" y2="5" stroke="#f472b6" stroke-width="1.8" stroke-linecap="round"/>
<line x1="5" y1="-5" x2="-5" y2="5" stroke="#f472b6" stroke-width="1.8" stroke-linecap="round"/>
<circle cx="0" cy="0" r="8" fill="none" stroke="#f472b6" stroke-width="1" opacity="0.5"/>
</g>
<text class="label" x="320" y="-2" font-size="9" fill="#f472b6" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif">closes</text>
<!-- signal flowing out after close -->
<path d="M312,9 L400,9" stroke="#f472b6" stroke-width="1" fill="none" stroke-dasharray="3 3"/>
<text class="code-sm" x="410" y="12" fill="#f472b6" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9.5px">struct{}{}</text>
</g>
<!-- Err() result -->
<g transform="translate(0, 94)">
<text class="code-sm" x="0" y="10" fill="#94a3b8" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9.5px">ctx.Err()</text>
<!-- nil while active -->
<text class="code-sm" x="180" y="10" fill="#64748b" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9.5px">nil</text>
<!-- after cancel: Canceled -->
<g transform="translate(500, 0)">
<rect x="0" y="0" width="128" height="18" rx="4" fill="none" stroke="#ec4899" stroke-width="0.8"/>
<text class="code-sm" x="64" y="12" text-anchor="middle" fill="#f472b6" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9.5px">context.Canceled</text>
</g>
</g>
<!-- DeadlineExceeded variant -->
<g transform="translate(0, 118)">
<text class="label" x="500" y="10" font-size="9" fill="#64748b" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif">or</text>
<g transform="translate(500, 20)">
<rect x="0" y="-8" width="170" height="18" rx="4" fill="none" stroke="#f59e0b" stroke-width="0.8"/>
<text class="code-sm" x="85" y="4" text-anchor="middle" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9.5px">context.DeadlineExceeded</text>
</g>
</g>
</g>
<!-- ================================================================ -->
<!-- RIGHT PANEL -->
<!-- ================================================================ -->
<g transform="translate(860, 88)">
<rect width="310" height="590" rx="14" fill="url(#g-panel)" stroke="#334155" stroke-width="1"/>
<!-- ── Context Functions ───────────────────────────────────────── -->
<text class="group-label" x="24" y="28" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">Context Functions</text>
<g transform="translate(24, 46)">
<!-- Background() -->
<rect x="0" y="0" width="262" height="24" rx="4" fill="none" stroke="#334155" stroke-width="0.5"/>
<circle cx="10" cy="12" r="4" fill="#3b82f6" opacity="0.8"/>
<text class="code" x="22" y="16" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="11px">Background()</text>
<text class="note" x="140" y="16" font-size="9" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" fill="#64748b">top-level root</text>
<!-- WithCancel(parent) -->
<rect x="0" y="32" width="262" height="24" rx="4" fill="none" stroke="#334155" stroke-width="0.5"/>
<circle cx="10" cy="44" r="4" fill="#3b82f6" opacity="0.8"/>
<text class="code" x="22" y="48" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="11px">WithCancel(parent)</text>
<text class="note" x="192" y="48" font-size="9" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" fill="#64748b">manual</text>
<!-- WithTimeout(parent, d) -->
<rect x="0" y="64" width="262" height="24" rx="4" fill="none" stroke="#334155" stroke-width="0.5"/>
<circle cx="10" cy="76" r="4" fill="#f59e0b" opacity="0.8"/>
<text class="code" x="22" y="80" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="11px">WithTimeout(parent, d)</text>
<text class="note" x="212" y="80" font-size="9" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" fill="#64748b">auto</text>
<!-- WithDeadline(parent, t) -->
<rect x="0" y="96" width="262" height="24" rx="4" fill="none" stroke="#334155" stroke-width="0.5"/>
<circle cx="10" cy="108" r="4" fill="#f59e0b" opacity="0.8"/>
<text class="code" x="22" y="112" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="11px">WithDeadline(parent, t)</text>
<text class="note" x="218" y="112" font-size="9" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" fill="#64748b">auto</text>
<!-- WithValue(parent, k, v) — separate category -->
<rect x="0" y="134" width="262" height="24" rx="4" fill="none" stroke="#334155" stroke-width="0.5"/>
<circle cx="10" cy="146" r="4" fill="#14b8a6" opacity="0.8"/>
<text class="code" x="22" y="150" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="11px">WithValue(parent, k, v)</text>
<text class="note" x="220" y="150" font-size="9" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" fill="#64748b">data</text>
</g>
<!-- ── Rules ───────────────────────────────────────────────────── -->
<text class="group-label" x="24" y="226" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">Rules</text>
<g transform="translate(24, 244)">
<text class="note" x="0" y="0" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">Pass as first parameter</text>
<text class="code-sm" x="0" y="16" fill="#64748b" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9.5px">func Do(ctx context.Context, ...)</text>
<text class="note" x="0" y="40" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">Never store in a struct</text>
<text class="code-sm" x="0" y="56" fill="#64748b" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9.5px">// always pass explicitly</text>
<text class="note" x="0" y="80" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">cancel() is idempotent</text>
<text class="code-sm" x="0" y="96" fill="#64748b" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9.5px">// safe to call multiple times</text>
<text class="note" x="0" y="120" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">Always defer cancel()</text>
<text class="code-sm" x="0" y="136" fill="#64748b" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9.5px">defer cancel() // prevents leak</text>
</g>
<!-- ── Key Insight ─────────────────────────────────────────────── -->
<text class="group-label" x="24" y="414" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">Key Insight</text>
<g transform="translate(24, 432)">
<rect x="-4" y="0" width="270" height="88" rx="8" fill="none" stroke="#334155" stroke-width="0.5"/>
<rect x="-4" y="0" width="4" height="88" rx="2" fill="url(#g-accent)"/>
<text class="quote" x="14" y="22" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="12px" font-weight="600" fill="#f1f5f9">Context is Go's structured</text>
<text class="quote" x="14" y="40" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="12px" font-weight="600" fill="#f1f5f9">concurrency primitive — it turns</text>
<text class="quote" x="14" y="58" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="12px" font-weight="600" fill="#f1f5f9">a goroutine tree into a</text>
<text class="quote" x="14" y="76" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="12px" font-weight="600" fill="#f1f5f9">controllable unit.</text>
</g>
<!-- ── WithValue note ──────────────────────────────────────────── -->
<g transform="translate(24, 540)">
<text class="quote-note" x="0" y="0" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">WithValue carries request-scoped data</text>
<text class="quote-note" x="0" y="16" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">(trace IDs, auth tokens) — not config.</text>
<text class="quote-note" x="0" y="32" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b">Keys should be unexported types.</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 27 KiB

369
testdata/svg/errgroup.svg vendored Normal file
View file

@ -0,0 +1,369 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 580">
<defs>
<!-- dot grid pattern -->
<pattern id="dots" width="40" height="40" patternUnits="userSpaceOnUse">
<circle cx="20" cy="20" r="0.8" fill="#94a3b8" opacity="0.05"/>
</pattern>
<!-- drop shadow -->
<filter id="shadow" x="-6%" y="-6%" width="112%" height="116%">
<feDropShadow dx="1" dy="2" stdDeviation="5" flood-color="#000" flood-opacity="0.35"/>
</filter>
<!-- title glow -->
<filter id="title-glow" x="-10%" y="-10%" width="120%" height="120%">
<feGaussianBlur in="SourceAlpha" stdDeviation="8" result="blur"/>
<feFlood flood-color="#60a5fa" flood-opacity="0.25" result="color"/>
<feComposite in="color" in2="blur" operator="in" result="glow"/>
<feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<!-- arrow markers -->
<marker id="arr-teal" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#2dd4bf"/>
</marker>
<marker id="arr-rose" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#f472b6"/>
</marker>
<marker id="arr-blue" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#60a5fa"/>
</marker>
<marker id="arr-amber" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#fbbf24"/>
</marker>
<!-- right panel gradient -->
<linearGradient id="g-panel" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0b1120" stop-opacity="0.95"/>
<stop offset="100%" stop-color="#080d1a" stop-opacity="0.98"/>
</linearGradient>
<!-- quote accent stripe -->
<linearGradient id="g-accent" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#3b82f6"/>
<stop offset="33%" stop-color="#14b8a6"/>
<stop offset="66%" stop-color="#f59e0b"/>
<stop offset="100%" stop-color="#ec4899"/>
</linearGradient>
<!-- ambient glows -->
<radialGradient id="glow-happy" cx="242" cy="300" r="300" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#14b8a6" stop-opacity="0.05"/>
<stop offset="100%" stop-color="#14b8a6" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-error" cx="640" cy="300" r="300" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#ec4899" stop-opacity="0.05"/>
<stop offset="100%" stop-color="#ec4899" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-limit" cx="430" cy="520" r="250" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#f59e0b" stop-opacity="0.04"/>
<stop offset="100%" stop-color="#f59e0b" stop-opacity="0"/>
</radialGradient>
<!-- hub gradient — dark fills with subtle color hint -->
<radialGradient id="hub-teal" cx="0.4" cy="0.35" r="0.7">
<stop offset="0%" stop-color="#134e4a"/>
<stop offset="100%" stop-color="#0f2a2a"/>
</radialGradient>
<radialGradient id="hub-rose" cx="0.4" cy="0.35" r="0.7">
<stop offset="0%" stop-color="#4a1942"/>
<stop offset="100%" stop-color="#1a0f1e"/>
</radialGradient>
<!-- cancel pulse -->
<filter id="cancel-glow" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur in="SourceAlpha" stdDeviation="3" result="blur"/>
<feFlood flood-color="#ec4899" flood-opacity="0.25" result="color"/>
<feComposite in="color" in2="blur" operator="in" result="glow"/>
<feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<!-- background -->
<rect width="1200" height="580" fill="url(#dots)"/>
<rect width="1200" height="580" fill="url(#glow-happy)"/>
<rect width="1200" height="580" fill="url(#glow-error)"/>
<rect width="1200" height="580" fill="url(#glow-limit)"/>
<!-- ================================================================ -->
<!-- TITLE -->
<!-- ================================================================ -->
<text x="52" y="42" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="22" font-weight="700" fill="#f1f5f9" filter="url(#title-glow)">Go errgroup — Structured Concurrency</text>
<text x="52" y="64" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="12" fill="#64748b" letter-spacing="2">launch · monitor · cancel on failure · collect errors</text>
<!-- thin separator -->
<line x1="52" y1="76" x2="830" y2="76" stroke="#1e293b" stroke-width="1"/>
<!-- ================================================================ -->
<!-- LEFT HALF: HAPPY PATH -->
<!-- ================================================================ -->
<text x="62" y="104" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="11" font-weight="700" fill="#14b8a6" letter-spacing="3" opacity="0.8">HAPPY PATH</text>
<!-- code label at top -->
<text x="72" y="130" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="10.5" fill="#e2e8f0" opacity="0.85">g, ctx := errgroup.WithContext(ctx)</text>
<!-- vertical line from code to hub -->
<line x1="222" y1="138" x2="222" y2="175" stroke="#2dd4bf" stroke-width="1.5" stroke-dasharray="4,3" opacity="0.5"/>
<!-- central hub circle -->
<circle cx="222" cy="200" r="24" fill="url(#hub-teal)" stroke="#14b8a6" stroke-width="1.5" filter="url(#shadow)"/>
<text x="222" y="196" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" font-weight="700" fill="#2dd4bf">err</text>
<text x="222" y="207" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" font-weight="700" fill="#2dd4bf">group</text>
<!-- fan-out lines from hub to goroutines -->
<!-- G1 -->
<line x1="204" y1="217" x2="102" y2="275" stroke="#2dd4bf" stroke-width="1.2" opacity="0.6" marker-end="url(#arr-teal)"/>
<!-- G2 -->
<line x1="212" y1="222" x2="172" y2="275" stroke="#2dd4bf" stroke-width="1.2" opacity="0.6" marker-end="url(#arr-teal)"/>
<!-- G3 -->
<line x1="232" y1="222" x2="272" y2="275" stroke="#2dd4bf" stroke-width="1.2" opacity="0.6" marker-end="url(#arr-teal)"/>
<!-- G4 -->
<line x1="240" y1="217" x2="342" y2="275" stroke="#2dd4bf" stroke-width="1.2" opacity="0.6" marker-end="url(#arr-teal)"/>
<!-- goroutine badges — happy path -->
<!-- G1 -->
<rect x="72" y="278" width="60" height="28" rx="6" fill="none" stroke="#14b8a6" stroke-width="1" filter="url(#shadow)"/>
<text x="92" y="296" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="11" font-weight="600" fill="#2dd4bf">G1</text>
<circle cx="140" cy="292" r="6" fill="#14b8a6" opacity="0.4"/>
<path d="M136,292 L139,295 L144,289" stroke="#0f172a" stroke-width="1.5" fill="none"/>
<!-- G2 -->
<rect x="142" y="278" width="60" height="28" rx="6" fill="none" stroke="#14b8a6" stroke-width="1" filter="url(#shadow)"/>
<text x="162" y="296" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="11" font-weight="600" fill="#2dd4bf">G2</text>
<circle cx="210" cy="292" r="6" fill="#14b8a6" opacity="0.4"/>
<path d="M206,292 L209,295 L214,289" stroke="#0f172a" stroke-width="1.5" fill="none"/>
<!-- G3 -->
<rect x="242" y="278" width="60" height="28" rx="6" fill="none" stroke="#14b8a6" stroke-width="1" filter="url(#shadow)"/>
<text x="262" y="296" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="11" font-weight="600" fill="#2dd4bf">G3</text>
<circle cx="310" cy="292" r="6" fill="#14b8a6" opacity="0.4"/>
<path d="M306,292 L309,295 L314,289" stroke="#0f172a" stroke-width="1.5" fill="none"/>
<!-- G4 -->
<rect x="312" y="278" width="60" height="28" rx="6" fill="none" stroke="#14b8a6" stroke-width="1" filter="url(#shadow)"/>
<text x="332" y="296" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="11" font-weight="600" fill="#2dd4bf">G4</text>
<circle cx="380" cy="292" r="6" fill="#14b8a6" opacity="0.4"/>
<path d="M376,292 L379,295 L384,289" stroke="#0f172a" stroke-width="1.5" fill="none"/>
<!-- return lines from goroutines back to wait bar -->
<line x1="102" y1="310" x2="102" y2="350" stroke="#2dd4bf" stroke-width="1" opacity="0.4" stroke-dasharray="3,3"/>
<line x1="172" y1="310" x2="172" y2="350" stroke="#2dd4bf" stroke-width="1" opacity="0.4" stroke-dasharray="3,3"/>
<line x1="272" y1="310" x2="272" y2="350" stroke="#2dd4bf" stroke-width="1" opacity="0.4" stroke-dasharray="3,3"/>
<line x1="342" y1="310" x2="342" y2="350" stroke="#2dd4bf" stroke-width="1" opacity="0.4" stroke-dasharray="3,3"/>
<!-- small teal checkmarks on return lines -->
<path d="M97,335 L100,338 L107,332" stroke="#2dd4bf" stroke-width="1.2" fill="none" opacity="0.7"/>
<path d="M167,335 L170,338 L177,332" stroke="#2dd4bf" stroke-width="1.2" fill="none" opacity="0.7"/>
<path d="M267,335 L270,338 L277,332" stroke="#2dd4bf" stroke-width="1.2" fill="none" opacity="0.7"/>
<path d="M337,335 L340,338 L347,332" stroke="#2dd4bf" stroke-width="1.2" fill="none" opacity="0.7"/>
<!-- Wait() collection bar -->
<rect x="72" y="352" width="340" height="28" rx="6" fill="none" stroke="#14b8a6" stroke-width="1.2" filter="url(#shadow)"/>
<text x="152" y="370" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="11" fill="#2dd4bf">g.Wait()</text>
<text x="248" y="370" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="11" fill="#94a3b8">→ nil</text>
<!-- small teal "all ok" note -->
<text x="222" y="400" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="10" fill="#64748b">all goroutines returned nil</text>
<!-- ================================================================ -->
<!-- RIGHT HALF (center): ERROR PATH -->
<!-- ================================================================ -->
<text x="462" y="104" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="11" font-weight="700" fill="#ec4899" letter-spacing="3" opacity="0.8">ERROR PATH</text>
<!-- code label at top -->
<text x="472" y="130" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="10.5" fill="#e2e8f0" opacity="0.85">g, ctx := errgroup.WithContext(ctx)</text>
<!-- vertical line from code to hub -->
<line x1="622" y1="138" x2="622" y2="175" stroke="#f472b6" stroke-width="1.5" stroke-dasharray="4,3" opacity="0.5"/>
<!-- central hub circle — error state -->
<circle cx="622" cy="200" r="24" fill="url(#hub-rose)" stroke="#ec4899" stroke-width="1.5" filter="url(#shadow)"/>
<text x="622" y="196" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" font-weight="700" fill="#f472b6">err</text>
<text x="622" y="207" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" font-weight="700" fill="#f472b6">group</text>
<!-- fan-out lines from hub to goroutines -->
<line x1="604" y1="217" x2="502" y2="275" stroke="#f472b6" stroke-width="1.2" opacity="0.6" marker-end="url(#arr-rose)"/>
<line x1="612" y1="222" x2="572" y2="275" stroke="#f472b6" stroke-width="1.2" opacity="0.6" marker-end="url(#arr-rose)"/>
<line x1="632" y1="222" x2="672" y2="275" stroke="#f472b6" stroke-width="1.2" opacity="0.6" marker-end="url(#arr-rose)"/>
<line x1="640" y1="217" x2="742" y2="275" stroke="#f472b6" stroke-width="1.2" opacity="0.6" marker-end="url(#arr-rose)"/>
<!-- goroutine badges — error path -->
<!-- G1 — cancelled -->
<rect x="472" y="278" width="60" height="28" rx="6" fill="none" stroke="#ec4899" stroke-width="1" stroke-dasharray="4,2" opacity="0.6"/>
<text x="492" y="296" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="11" font-weight="600" fill="#f472b6" opacity="0.6">G1</text>
<text x="492" y="323" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" fill="#ec4899" opacity="0.6">cancelled</text>
<!-- G2 — error source -->
<rect x="542" y="278" width="60" height="28" rx="6" fill="none" stroke="#ec4899" stroke-width="1.5" filter="url(#cancel-glow)"/>
<text x="562" y="296" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="11" font-weight="600" fill="#f472b6">G2</text>
<!-- X mark for G2 -->
<circle cx="610" cy="292" r="6" fill="#ec4899" opacity="0.4"/>
<path d="M606,288 L614,296 M614,288 L606,296" stroke="#0f172a" stroke-width="1.8" fill="none"/>
<text x="572" y="323" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" fill="#ec4899" font-weight="700">error!</text>
<!-- G3 — cancelled -->
<rect x="642" y="278" width="60" height="28" rx="6" fill="none" stroke="#ec4899" stroke-width="1" stroke-dasharray="4,2" opacity="0.6"/>
<text x="662" y="296" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="11" font-weight="600" fill="#f472b6" opacity="0.6">G3</text>
<text x="662" y="323" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" fill="#ec4899" opacity="0.6">cancelled</text>
<!-- G4 — cancelled -->
<rect x="712" y="278" width="60" height="28" rx="6" fill="none" stroke="#ec4899" stroke-width="1" stroke-dasharray="4,2" opacity="0.6"/>
<text x="732" y="296" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="11" font-weight="600" fill="#f472b6" opacity="0.6">G4</text>
<text x="732" y="323" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" fill="#ec4899" opacity="0.6">cancelled</text>
<!-- cancel signal propagation — dashed rose lines from hub outward -->
<line x1="600" y1="206" x2="502" y2="260" stroke="#ec4899" stroke-width="1" stroke-dasharray="5,3" opacity="0.4"/>
<line x1="640" y1="206" x2="672" y2="260" stroke="#ec4899" stroke-width="1" stroke-dasharray="5,3" opacity="0.4"/>
<line x1="645" y1="200" x2="742" y2="260" stroke="#ec4899" stroke-width="1" stroke-dasharray="5,3" opacity="0.4"/>
<!-- "cancel" label on propagation -->
<text x="490" y="248" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="8" fill="#ec4899" opacity="0.5" transform="rotate(-30,490,248)">ctx.cancel</text>
<text x="700" y="248" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="8" fill="#ec4899" opacity="0.5" transform="rotate(28,700,248)">ctx.cancel</text>
<!-- error return line from G2 to wait -->
<line x1="572" y1="310" x2="572" y2="350" stroke="#ec4899" stroke-width="1.5" opacity="0.7"/>
<!-- other return lines — faded -->
<line x1="502" y1="310" x2="502" y2="350" stroke="#ec4899" stroke-width="0.8" opacity="0.2" stroke-dasharray="3,3"/>
<line x1="672" y1="310" x2="672" y2="350" stroke="#ec4899" stroke-width="0.8" opacity="0.2" stroke-dasharray="3,3"/>
<line x1="742" y1="310" x2="742" y2="350" stroke="#ec4899" stroke-width="0.8" opacity="0.2" stroke-dasharray="3,3"/>
<!-- Wait() collection bar — error state -->
<rect x="472" y="352" width="340" height="28" rx="6" fill="none" stroke="#ec4899" stroke-width="1.2" filter="url(#shadow)"/>
<text x="552" y="370" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="11" fill="#f472b6">g.Wait()</text>
<text x="640" y="370" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="11" fill="#ec4899">→ err</text>
<!-- note below wait bar -->
<text x="642" y="400" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="10" fill="#64748b">first error wins — others cancelled via ctx</text>
<!-- ================================================================ -->
<!-- BOTTOM STRIP: BOUNDED CONCURRENCY -->
<!-- ================================================================ -->
<line x1="52" y1="420" x2="830" y2="420" stroke="#1e293b" stroke-width="1"/>
<text x="62" y="446" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="11" font-weight="700" fill="#f59e0b" letter-spacing="3" opacity="0.8">BOUNDED CONCURRENCY</text>
<!-- SetLimit code label -->
<text x="260" y="446" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="10.5" fill="#e2e8f0" opacity="0.85">g.SetLimit(3)</text>
<!-- queue of waiting goroutines — left side -->
<!-- waiting badges (dimmed) -->
<rect x="62" y="462" width="44" height="22" rx="5" fill="none" stroke="#f59e0b" stroke-width="0.8" stroke-dasharray="3,2" opacity="0.4"/>
<text x="84" y="477" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#fbbf24" opacity="0.4">G6</text>
<rect x="112" y="462" width="44" height="22" rx="5" fill="none" stroke="#f59e0b" stroke-width="0.8" stroke-dasharray="3,2" opacity="0.4"/>
<text x="134" y="477" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#fbbf24" opacity="0.4">G5</text>
<rect x="162" y="462" width="44" height="22" rx="5" fill="none" stroke="#f59e0b" stroke-width="0.8" stroke-dasharray="3,2" opacity="0.5"/>
<text x="184" y="477" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#fbbf24" opacity="0.5">G4</text>
<!-- "waiting" label -->
<text x="130" y="500" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" fill="#64748b">queued</text>
<!-- funnel / gate shape -->
<path d="M218,458 L258,462 L258,486 L218,490 Z" fill="none" stroke="#f59e0b" stroke-width="1.2" opacity="0.5"/>
<path d="M258,462 L288,466 L288,482 L258,486 Z" fill="#f59e0b" fill-opacity="0.08" stroke="#f59e0b" stroke-width="1.2" opacity="0.7"/>
<text x="273" y="478" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" fill="#fbbf24" font-weight="700">N=3</text>
<!-- arrows through gate -->
<line x1="290" y1="473" x2="308" y2="473" stroke="#fbbf24" stroke-width="1.2" opacity="0.6" marker-end="url(#arr-amber)"/>
<!-- active goroutines — 3 passing through -->
<rect x="316" y="456" width="44" height="22" rx="5" fill="none" stroke="#f59e0b" stroke-width="1.2" filter="url(#shadow)"/>
<text x="338" y="471" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" font-weight="600" fill="#fbbf24">G1</text>
<rect x="368" y="456" width="44" height="22" rx="5" fill="none" stroke="#f59e0b" stroke-width="1.2" filter="url(#shadow)"/>
<text x="390" y="471" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" font-weight="600" fill="#fbbf24">G2</text>
<rect x="420" y="456" width="44" height="22" rx="5" fill="none" stroke="#f59e0b" stroke-width="1.2" filter="url(#shadow)"/>
<text x="442" y="471" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" font-weight="600" fill="#fbbf24">G3</text>
<!-- "active" label -->
<text x="390" y="500" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" fill="#64748b">active (max 3)</text>
<!-- result arrows flowing out -->
<line x1="466" y1="467" x2="490" y2="467" stroke="#fbbf24" stroke-width="1" opacity="0.5" marker-end="url(#arr-amber)"/>
<!-- results collector -->
<rect x="498" y="456" width="70" height="22" rx="5" fill="none" stroke="#f59e0b" stroke-width="0.8" opacity="0.6"/>
<text x="533" y="471" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#fbbf24" opacity="0.7">results</text>
<!-- description -->
<text x="400" y="530" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="10" fill="#cbd5e1" opacity="0.7">at most 3 concurrent goroutines — excess calls to Go() block until a slot opens</text>
<!-- ================================================================ -->
<!-- RIGHT PANEL -->
<!-- ================================================================ -->
<rect x="860" y="88" width="310" height="480" rx="10" fill="url(#g-panel)" filter="url(#shadow)"/>
<!-- panel border -->
<rect x="860" y="88" width="310" height="480" rx="10" fill="none" stroke="#1e293b" stroke-width="1"/>
<!-- API section -->
<text x="884" y="118" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="12" font-weight="700" fill="#f1f5f9" letter-spacing="1">API</text>
<line x1="884" y1="125" x2="1146" y2="125" stroke="#1e293b" stroke-width="0.5"/>
<!-- API items -->
<text x="884" y="146" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="10" fill="#e2e8f0">errgroup.WithContext(ctx)</text>
<text x="884" y="160" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" fill="#94a3b8">creates group + derived context</text>
<text x="884" y="182" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="10" fill="#e2e8f0">g.Go(func() error)</text>
<text x="884" y="196" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" fill="#94a3b8">launches goroutine in the group</text>
<text x="884" y="218" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="10" fill="#e2e8f0">g.Wait() error</text>
<text x="884" y="232" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" fill="#94a3b8">blocks, returns first error</text>
<text x="884" y="254" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="10" fill="#e2e8f0">g.SetLimit(n)</text>
<text x="884" y="268" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" fill="#94a3b8">bounds concurrency to n goroutines</text>
<!-- vs WaitGroup section -->
<text x="884" y="300" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="12" font-weight="700" fill="#f1f5f9" letter-spacing="1">vs WaitGroup</text>
<line x1="884" y1="307" x2="1146" y2="307" stroke="#1e293b" stroke-width="0.5"/>
<!-- WaitGroup icon — simple counter -->
<rect x="884" y="318" width="120" height="36" rx="6" fill="none" stroke="#3b82f6" stroke-width="0.8" opacity="0.6"/>
<text x="900" y="332" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#60a5fa">WaitGroup</text>
<text x="900" y="346" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" fill="#94a3b8">fire and forget</text>
<!-- simple counter dots -->
<circle cx="990" cy="332" r="3" fill="#3b82f6" opacity="0.4"/>
<circle cx="998" cy="332" r="3" fill="#3b82f6" opacity="0.4"/>
<circle cx="990" cy="342" r="3" fill="#3b82f6" opacity="0.4"/>
<!-- errgroup icon — hub with antenna -->
<rect x="884" y="362" width="120" height="36" rx="6" fill="none" stroke="#14b8a6" stroke-width="0.8" opacity="0.8"/>
<text x="900" y="376" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#2dd4bf">errgroup</text>
<text x="900" y="390" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" fill="#94a3b8">fire, monitor, cancel</text>
<!-- hub with error antenna -->
<circle cx="993" cy="380" r="5" fill="none" stroke="#14b8a6" stroke-width="0.8" opacity="0.7"/>
<line x1="993" y1="375" x2="993" y2="366" stroke="#14b8a6" stroke-width="1" opacity="0.6"/>
<line x1="993" y1="366" x2="987" y2="362" stroke="#ec4899" stroke-width="0.8" opacity="0.7"/>
<line x1="993" y1="366" x2="999" y2="362" stroke="#ec4899" stroke-width="0.8" opacity="0.7"/>
<!-- radiate lines from hub -->
<line x1="998" y1="380" x2="1004" y2="376" stroke="#2dd4bf" stroke-width="0.7" opacity="0.5"/>
<line x1="998" y1="380" x2="1004" y2="384" stroke="#2dd4bf" stroke-width="0.7" opacity="0.5"/>
<line x1="988" y1="380" x2="982" y2="376" stroke="#2dd4bf" stroke-width="0.7" opacity="0.5"/>
<line x1="988" y1="380" x2="982" y2="384" stroke="#2dd4bf" stroke-width="0.7" opacity="0.5"/>
<!-- Key Insight section -->
<text x="884" y="422" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="12" font-weight="700" fill="#f1f5f9" letter-spacing="1">Key Insight</text>
<line x1="884" y1="429" x2="1146" y2="429" stroke="#1e293b" stroke-width="0.5"/>
<!-- quote box -->
<rect x="884" y="440" width="286" height="76" rx="6" fill="none" stroke="#1e293b" stroke-width="0.8"/>
<!-- accent stripe -->
<rect x="884" y="440" width="3" height="76" rx="1.5" fill="url(#g-accent)"/>
<text x="898" y="458" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="10" fill="#cbd5e1" font-style="italic">
<tspan x="898" dy="0">errgroup is Go's answer to structured</tspan>
<tspan x="898" dy="14">concurrency — goroutines have an owner</tspan>
<tspan x="898" dy="14">that waits for them and handles</tspan>
<tspan x="898" dy="14">their errors.</tspan>
</text>
<!-- package import note -->
<text x="884" y="540" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#64748b">golang.org/x/sync/errgroup</text>
</svg>

After

Width:  |  Height:  |  Size: 25 KiB

502
testdata/svg/go-concurrency.svg vendored Normal file
View file

@ -0,0 +1,502 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 790">
<defs>
<!-- drop shadow -->
<filter id="shadow" x="-6%" y="-6%" width="112%" height="116%">
<feDropShadow dx="1" dy="2" stdDeviation="5" flood-color="#000" flood-opacity="0.35"/>
</filter>
<!-- title glow -->
<filter id="title-glow" x="-10%" y="-10%" width="120%" height="120%">
<feGaussianBlur in="SourceAlpha" stdDeviation="8" result="blur"/>
<feFlood flood-color="#60a5fa" flood-opacity="0.25" result="color"/>
<feComposite in="color" in2="blur" operator="in" result="glow"/>
<feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<!-- arrow markers -->
<marker id="arr-blue" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#60a5fa"/>
</marker>
<marker id="arr-teal" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#2dd4bf"/>
</marker>
<marker id="arr-amber" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#fbbf24"/>
</marker>
<marker id="arr-rose" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#f472b6"/>
</marker>
<!-- ============================================================ -->
<!-- GRADIENTS -->
<!-- ============================================================ -->
<!-- header gradients — slight diagonal for light-source feel -->
<linearGradient id="g-found" x1="0" y1="0" x2="0.3" y2="1">
<stop offset="0%" stop-color="#4b92f7"/><stop offset="100%" stop-color="#2563eb"/>
</linearGradient>
<linearGradient id="g-comm" x1="0" y1="0" x2="0.3" y2="1">
<stop offset="0%" stop-color="#20c9b4"/><stop offset="100%" stop-color="#0d9488"/>
</linearGradient>
<linearGradient id="g-sync" x1="0" y1="0" x2="0.3" y2="1">
<stop offset="0%" stop-color="#fbbd3e"/><stop offset="100%" stop-color="#d97706"/>
</linearGradient>
<linearGradient id="g-pat" x1="0" y1="0" x2="0.3" y2="1">
<stop offset="0%" stop-color="#f472b6"/><stop offset="100%" stop-color="#db2777"/>
</linearGradient>
<!-- box body gradients — subtle top-light effect -->
<linearGradient id="gb-found" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#244b6e"/><stop offset="100%" stop-color="#1a3250"/>
</linearGradient>
<linearGradient id="gb-comm" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#1a5c56"/><stop offset="100%" stop-color="#0f3f3b"/>
</linearGradient>
<linearGradient id="gb-sync" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#5c2a08"/><stop offset="100%" stop-color="#3a1a03"/>
</linearGradient>
<linearGradient id="gb-pat" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#5e1438"/><stop offset="100%" stop-color="#3d0a24"/>
</linearGradient>
<!-- zone radial gradients — glow from top-left corner -->
<radialGradient id="gz-found" cx="0.2" cy="0.15" r="0.85">
<stop offset="0%" stop-color="#3b82f6" stop-opacity="0.08"/>
<stop offset="100%" stop-color="#3b82f6" stop-opacity="0.01"/>
</radialGradient>
<radialGradient id="gz-comm" cx="0.2" cy="0.15" r="0.85">
<stop offset="0%" stop-color="#14b8a6" stop-opacity="0.08"/>
<stop offset="100%" stop-color="#14b8a6" stop-opacity="0.01"/>
</radialGradient>
<radialGradient id="gz-sync" cx="0.2" cy="0.15" r="0.85">
<stop offset="0%" stop-color="#f59e0b" stop-opacity="0.08"/>
<stop offset="100%" stop-color="#f59e0b" stop-opacity="0.01"/>
</radialGradient>
<radialGradient id="gz-pat" cx="0.2" cy="0.15" r="0.85">
<stop offset="0%" stop-color="#ec4899" stop-opacity="0.08"/>
<stop offset="100%" stop-color="#ec4899" stop-opacity="0.01"/>
</radialGradient>
<!-- background ambient glow -->
<radialGradient id="glow-found" cx="212" cy="270" r="320" fx="212" fy="270" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#3b82f6" stop-opacity="0.04"/>
<stop offset="100%" stop-color="#3b82f6" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-comm" cx="598" cy="270" r="320" fx="598" fy="270" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#14b8a6" stop-opacity="0.04"/>
<stop offset="100%" stop-color="#14b8a6" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-sync" cx="212" cy="600" r="320" fx="212" fy="600" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#f59e0b" stop-opacity="0.03"/>
<stop offset="100%" stop-color="#f59e0b" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-pat" cx="598" cy="600" r="320" fx="598" fy="600" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#ec4899" stop-opacity="0.03"/>
<stop offset="100%" stop-color="#ec4899" stop-opacity="0"/>
</radialGradient>
<!-- right panel gradient -->
<linearGradient id="g-panel" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#141d2e" stop-opacity="0.9"/>
<stop offset="100%" stop-color="#0b1120" stop-opacity="0.95"/>
</linearGradient>
<!-- quote accent stripe — 4-color vertical gradient -->
<linearGradient id="g-accent" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#3b82f6"/>
<stop offset="33%" stop-color="#14b8a6"/>
<stop offset="66%" stop-color="#f59e0b"/>
<stop offset="100%" stop-color="#ec4899"/>
</linearGradient>
<!-- connection gradients — transition between category colors -->
<linearGradient id="gc-blue-teal" gradientUnits="userSpaceOnUse" x1="372" y1="169" x2="438" y2="169">
<stop offset="0%" stop-color="#60a5fa"/><stop offset="100%" stop-color="#2dd4bf"/>
</linearGradient>
<linearGradient id="gc-blue-amber-v" gradientUnits="userSpaceOnUse" x1="0" y1="208" x2="0" y2="476">
<stop offset="0%" stop-color="#60a5fa"/><stop offset="100%" stop-color="#fbbf24"/>
</linearGradient>
<linearGradient id="gc-teal-rose-v" gradientUnits="userSpaceOnUse" x1="0" y1="208" x2="0" y2="476">
<stop offset="0%" stop-color="#2dd4bf"/><stop offset="100%" stop-color="#f472b6"/>
</linearGradient>
<linearGradient id="gc-blue-rose" gradientUnits="userSpaceOnUse" x1="372" y1="150" x2="438" y2="515">
<stop offset="0%" stop-color="#60a5fa"/><stop offset="100%" stop-color="#f472b6"/>
</linearGradient>
<linearGradient id="gc-amber-rose" gradientUnits="userSpaceOnUse" x1="372" y1="515" x2="438" y2="613">
<stop offset="0%" stop-color="#fbbf24"/><stop offset="100%" stop-color="#f472b6"/>
</linearGradient>
<linearGradient id="gc-blue-teal-diag" gradientUnits="userSpaceOnUse" x1="372" y1="267" x2="438" y2="360">
<stop offset="0%" stop-color="#60a5fa"/><stop offset="100%" stop-color="#2dd4bf"/>
</linearGradient>
<linearGradient id="gc-amber-blue-v" gradientUnits="userSpaceOnUse" x1="0" y1="404" x2="0" y2="574">
<stop offset="0%" stop-color="#60a5fa"/><stop offset="100%" stop-color="#fbbf24"/>
</linearGradient>
<!-- icons -->
<symbol id="ico-goroutine" viewBox="0 0 16 16">
<path d="M8 1l2 4h-1.5v4H12l-4 6-4-6h2.5V5H5z" fill="currentColor"/>
</symbol>
<symbol id="ico-channel" viewBox="0 0 16 16">
<path d="M1 8h14M11 4l4 4-4 4M5 4L1 8l4 4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="ico-buffer" viewBox="0 0 16 16">
<rect x="2" y="3" width="12" height="3" rx="1" fill="currentColor" opacity="0.5"/>
<rect x="2" y="7" width="12" height="3" rx="1" fill="currentColor" opacity="0.75"/>
<rect x="2" y="11" width="12" height="3" rx="1" fill="currentColor"/>
</symbol>
<symbol id="ico-select" viewBox="0 0 16 16">
<path d="M4 3v10M4 8h8M12 4l-4 4 4 4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="ico-context" viewBox="0 0 16 16">
<circle cx="8" cy="8" r="5" fill="none" stroke="currentColor" stroke-width="1.5"/>
<circle cx="8" cy="8" r="1.5" fill="currentColor"/>
<path d="M8 1v3M8 12v3M1 8h3M12 8h3" stroke="currentColor" stroke-width="1" stroke-linecap="round"/>
</symbol>
<symbol id="ico-atomic" viewBox="0 0 16 16">
<ellipse cx="8" cy="8" rx="6" ry="2.5" fill="none" stroke="currentColor" stroke-width="1" transform="rotate(0 8 8)"/>
<ellipse cx="8" cy="8" rx="6" ry="2.5" fill="none" stroke="currentColor" stroke-width="1" transform="rotate(60 8 8)"/>
<ellipse cx="8" cy="8" rx="6" ry="2.5" fill="none" stroke="currentColor" stroke-width="1" transform="rotate(120 8 8)"/>
<circle cx="8" cy="8" r="1.5" fill="currentColor"/>
</symbol>
<symbol id="ico-wait" viewBox="0 0 16 16">
<circle cx="8" cy="8" r="6" fill="none" stroke="currentColor" stroke-width="1.5"/>
<path d="M8 4v4l3 2" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</symbol>
<symbol id="ico-lock" viewBox="0 0 16 16">
<rect x="3" y="7" width="10" height="7" rx="1.5" fill="currentColor" opacity="0.8"/>
<path d="M5.5 7V5a2.5 2.5 0 015 0v2" fill="none" stroke="currentColor" stroke-width="1.5"/>
</symbol>
<symbol id="ico-bell" viewBox="0 0 16 16">
<path d="M8 1.5a4 4 0 00-4 4v3l-1 2h10l-1-2v-3a4 4 0 00-4-4z" fill="currentColor" opacity="0.8"/>
<path d="M6.5 12.5a1.5 1.5 0 003 0" fill="none" stroke="currentColor" stroke-width="1"/>
</symbol>
<symbol id="ico-fan" viewBox="0 0 16 16">
<path d="M8 14V6M8 6L3 2M8 6l5-4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="ico-workers" viewBox="0 0 16 16">
<circle cx="4" cy="4" r="2" fill="currentColor" opacity="0.7"/>
<circle cx="12" cy="4" r="2" fill="currentColor" opacity="0.7"/>
<circle cx="8" cy="3" r="2" fill="currentColor"/>
<path d="M1 14c0-3 2-5 5-5h4c3 0 5 2 5 5" fill="currentColor" opacity="0.4"/>
</symbol>
<symbol id="ico-pipe" viewBox="0 0 16 16">
<circle cx="3" cy="8" r="2" fill="none" stroke="currentColor" stroke-width="1.2"/>
<circle cx="8" cy="8" r="2" fill="none" stroke="currentColor" stroke-width="1.2"/>
<circle cx="13" cy="8" r="2" fill="none" stroke="currentColor" stroke-width="1.2"/>
<line x1="5" y1="8" x2="6" y2="8" stroke="currentColor" stroke-width="1.2"/>
<line x1="10" y1="8" x2="11" y2="8" stroke="currentColor" stroke-width="1.2"/>
</symbol>
</defs>
<style>
.bg { fill: #0f172a; }
.title { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 28px; font-weight: 700; fill: #f1f5f9; }
.subtitle { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 13px; font-weight: 400; fill: #94a3b8; }
.group-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 600; letter-spacing: 1.5px; text-transform: uppercase; }
.box-title { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 13px; font-weight: 700; fill: #fff; }
.box-desc { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 400; fill: #cbd5e1; }
.legend-text { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; fill: #94a3b8; }
.mini-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 600; }
.quote { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 12px; font-weight: 600; fill: #f1f5f9; }
.quote-note { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; fill: #64748b; }
.bx-f { fill: url(#gb-found); stroke: #3b82f6; stroke-width: 1.5; }
.bx-c { fill: url(#gb-comm); stroke: #14b8a6; stroke-width: 1.5; }
.bx-s { fill: url(#gb-sync); stroke: #f59e0b; stroke-width: 1.5; }
.bx-p { fill: url(#gb-pat); stroke: #ec4899; stroke-width: 1.5; }
.zone { stroke-opacity: 0.18; stroke-width: 1; stroke-dasharray: 6 3; }
.z-f { fill: url(#gz-found); stroke: #3b82f6; }
.z-c { fill: url(#gz-comm); stroke: #14b8a6; }
.z-s { fill: url(#gz-sync); stroke: #f59e0b; }
.z-p { fill: url(#gz-pat); stroke: #ec4899; }
.conn { fill: none; stroke-width: 1.8; opacity: 0.75; }
.c-bl { stroke: #60a5fa; }
.c-tl { stroke: #2dd4bf; }
.c-am { stroke: #fbbf24; }
.c-rs { stroke: #f472b6; }
.c-dash { stroke-dasharray: 6 4; }
</style>
<!-- background -->
<!-- dot grid -->
<g opacity="0.05">
<pattern id="dots" width="40" height="40" patternUnits="userSpaceOnUse">
<circle cx="20" cy="20" r="0.8" fill="#94a3b8"/>
</pattern>
<rect width="1200" height="790" fill="url(#dots)"/>
</g>
<!-- ambient background glows -->
<rect width="1200" height="790" fill="url(#glow-found)"/>
<rect width="1200" height="790" fill="url(#glow-comm)"/>
<rect width="1200" height="790" fill="url(#glow-sync)"/>
<rect width="1200" height="790" fill="url(#glow-pat)"/>
<!-- title -->
<g filter="url(#title-glow)">
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="28px" font-weight="700" fill="#f1f5f9" x="400" y="44" text-anchor="middle" dominant-baseline="central">Go Concurrency Model</text>
</g>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="400" fill="#94a3b8" x="400" y="68" text-anchor="middle" dominant-baseline="central">foundations · communication · synchronization · patterns</text>
<!--
GRID: zone-w=360 box-w=320 box-h=78 header-h=30
zone-pad=20 box-gap=20 text-left=box+24 icon-x=box+10
desc-top=header-bottom+16 desc-gap=15 bottom-pad>=14
-->
<!-- ============================================================== -->
<!-- ZONE: Foundation y=92 h=320 -->
<!-- box1=130 box2=228 box3=326 -->
<!-- ============================================================== -->
<rect stroke-opacity="0.18" stroke-width="1" stroke-dasharray="6 3" fill="url(#gz-found)" stroke="#3b82f6" x="32" y="92" width="360" height="320" rx="14"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px" x="52" y="116" fill="#60a5fa">Foundation</text>
<g filter="url(#shadow)">
<rect fill="url(#gb-found)" stroke="#3b82f6" stroke-width="1.5" x="52" y="130" width="320" height="78" rx="10"/>
<rect width="320" height="30" x="52" y="130" rx="10" fill="url(#g-found)"/>
<use href="#ico-goroutine" x="62" y="137" width="16" height="16" color="#fff"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700" fill="#fff" x="84" y="145" dominant-baseline="central">Goroutine</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="76" y="176" dominant-baseline="central">Lightweight concurrent function managed</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="76" y="191" dominant-baseline="central">by the Go runtime scheduler</text>
</g>
<g filter="url(#shadow)">
<rect fill="url(#gb-found)" stroke="#3b82f6" stroke-width="1.5" x="52" y="228" width="320" height="78" rx="10"/>
<rect width="320" height="30" x="52" y="228" rx="10" fill="url(#g-found)"/>
<use href="#ico-context" x="62" y="235" width="16" height="16" color="#fff"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700" fill="#fff" x="84" y="243" dominant-baseline="central">Context</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="76" y="274" dominant-baseline="central">Propagates cancellation, deadlines, and</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="76" y="289" dominant-baseline="central">values across goroutine boundaries</text>
</g>
<g filter="url(#shadow)">
<rect fill="url(#gb-found)" stroke="#3b82f6" stroke-width="1.5" x="52" y="326" width="320" height="78" rx="10"/>
<rect width="320" height="30" x="52" y="326" rx="10" fill="url(#g-found)"/>
<use href="#ico-atomic" x="62" y="333" width="16" height="16" color="#fff"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700" fill="#fff" x="84" y="341" dominant-baseline="central">Atomic Operations</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="76" y="372" dominant-baseline="central">Lock-free thread-safe primitives for</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="76" y="387" dominant-baseline="central">simple shared counters and flags</text>
</g>
<!-- ============================================================== -->
<!-- ZONE: Communication y=92 h=320 -->
<!-- box1=130 box2=228 box3=326 -->
<!-- ============================================================== -->
<rect stroke-opacity="0.18" stroke-width="1" stroke-dasharray="6 3" fill="url(#gz-comm)" stroke="#14b8a6" x="418" y="92" width="360" height="320" rx="14"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px" x="438" y="116" fill="#2dd4bf">Communication</text>
<g filter="url(#shadow)">
<rect fill="url(#gb-comm)" stroke="#14b8a6" stroke-width="1.5" x="438" y="130" width="320" height="78" rx="10"/>
<rect width="320" height="30" x="438" y="130" rx="10" fill="url(#g-comm)"/>
<use href="#ico-channel" x="448" y="137" width="16" height="16" color="#fff"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700" fill="#fff" x="470" y="145" dominant-baseline="central">Channels</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="462" y="176" dominant-baseline="central">Typed communication pipes between</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="462" y="191" dominant-baseline="central">goroutines — the core CSP primitive</text>
</g>
<g filter="url(#shadow)">
<rect fill="url(#gb-comm)" stroke="#14b8a6" stroke-width="1.5" x="438" y="228" width="320" height="78" rx="10"/>
<rect width="320" height="30" x="438" y="228" rx="10" fill="url(#g-comm)"/>
<use href="#ico-buffer" x="448" y="235" width="16" height="16" color="#fff"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700" fill="#fff" x="470" y="243" dominant-baseline="central">Buffered Channels</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="462" y="274" dominant-baseline="central">Channels with capacity to decouple</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="462" y="289" dominant-baseline="central">sender and receiver timing</text>
</g>
<g filter="url(#shadow)">
<rect fill="url(#gb-comm)" stroke="#14b8a6" stroke-width="1.5" x="438" y="326" width="320" height="78" rx="10"/>
<rect width="320" height="30" x="438" y="326" rx="10" fill="url(#g-comm)"/>
<use href="#ico-select" x="448" y="333" width="16" height="16" color="#fff"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700" fill="#fff" x="470" y="341" dominant-baseline="central">Select</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="462" y="372" dominant-baseline="central">Multiplexes across multiple channel</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="462" y="387" dominant-baseline="central">operations with blocking or default</text>
</g>
<!-- ============================================================== -->
<!-- ZONE: Synchronization y=438 h=320 -->
<!-- box1=476 box2=574 box3=672 -->
<!-- ============================================================== -->
<rect stroke-opacity="0.18" stroke-width="1" stroke-dasharray="6 3" fill="url(#gz-sync)" stroke="#f59e0b" x="32" y="438" width="360" height="320" rx="14"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px" x="52" y="462" fill="#fbbf24">Synchronization</text>
<g filter="url(#shadow)">
<rect fill="url(#gb-sync)" stroke="#f59e0b" stroke-width="1.5" x="52" y="476" width="320" height="78" rx="10"/>
<rect width="320" height="30" x="52" y="476" rx="10" fill="url(#g-sync)"/>
<use href="#ico-wait" x="62" y="483" width="16" height="16" color="#fff"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700" fill="#fff" x="84" y="491" dominant-baseline="central">WaitGroup</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="76" y="522" dominant-baseline="central">Wait for a collection of goroutines</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="76" y="537" dominant-baseline="central">to finish before proceeding</text>
</g>
<g filter="url(#shadow)">
<rect fill="url(#gb-sync)" stroke="#f59e0b" stroke-width="1.5" x="52" y="574" width="320" height="78" rx="10"/>
<rect width="320" height="30" x="52" y="574" rx="10" fill="url(#g-sync)"/>
<use href="#ico-lock" x="62" y="581" width="16" height="16" color="#fff"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700" fill="#fff" x="84" y="589" dominant-baseline="central">Mutex / RWMutex</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="76" y="620" dominant-baseline="central">Mutual exclusion for shared state —</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="76" y="635" dominant-baseline="central">RWMutex allows concurrent readers</text>
</g>
<g filter="url(#shadow)">
<rect fill="url(#gb-sync)" stroke="#f59e0b" stroke-width="1.5" x="52" y="672" width="320" height="78" rx="10"/>
<rect width="320" height="30" x="52" y="672" rx="10" fill="url(#g-sync)"/>
<use href="#ico-bell" x="62" y="679" width="16" height="16" color="#fff"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700" fill="#fff" x="84" y="687" dominant-baseline="central">sync.Cond</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="76" y="718" dominant-baseline="central">Coordinate goroutines waiting on</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="76" y="733" dominant-baseline="central">shared state changes via signals</text>
</g>
<!-- ============================================================== -->
<!-- ZONE: Patterns y=438 h=320 -->
<!-- box1=476 box2=574 box3=672 -->
<!-- ============================================================== -->
<rect stroke-opacity="0.18" stroke-width="1" stroke-dasharray="6 3" fill="url(#gz-pat)" stroke="#ec4899" x="418" y="438" width="360" height="320" rx="14"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px" x="438" y="462" fill="#f472b6">Patterns</text>
<g filter="url(#shadow)">
<rect fill="url(#gb-pat)" stroke="#ec4899" stroke-width="1.5" x="438" y="476" width="320" height="78" rx="10"/>
<rect width="320" height="30" x="438" y="476" rx="10" fill="url(#g-pat)"/>
<use href="#ico-fan" x="448" y="483" width="16" height="16" color="#fff"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700" fill="#fff" x="470" y="491" dominant-baseline="central">Fan-Out / Fan-In</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="462" y="522" dominant-baseline="central">Distribute work across goroutines,</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="462" y="537" dominant-baseline="central">merge results into a single channel</text>
</g>
<g filter="url(#shadow)">
<rect fill="url(#gb-pat)" stroke="#ec4899" stroke-width="1.5" x="438" y="574" width="320" height="78" rx="10"/>
<rect width="320" height="30" x="438" y="574" rx="10" fill="url(#g-pat)"/>
<use href="#ico-workers" x="448" y="581" width="16" height="16" color="#fff"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700" fill="#fff" x="470" y="589" dominant-baseline="central">Worker Pool</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="462" y="620" dominant-baseline="central">Fixed set of goroutines processing</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="462" y="635" dominant-baseline="central">tasks from a shared job queue</text>
</g>
<g filter="url(#shadow)">
<rect fill="url(#gb-pat)" stroke="#ec4899" stroke-width="1.5" x="438" y="672" width="320" height="78" rx="10"/>
<rect width="320" height="30" x="438" y="672" rx="10" fill="url(#g-pat)"/>
<use href="#ico-pipe" x="448" y="679" width="16" height="16" color="#fff"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="13px" font-weight="700" fill="#fff" x="470" y="687" dominant-baseline="central">Pipeline</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="462" y="718" dominant-baseline="central">Chain of stages connected by channels,</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#cbd5e1" x="462" y="733" dominant-baseline="central">each stage a goroutine reading/writing</text>
</g>
<!-- ============================================================== -->
<!-- CONNECTIONS -->
<!-- box bottoms: 208, 306, 404 | 554, 652, 750 -->
<!-- box tops: 130, 228, 326 | 476, 574, 672 -->
<!-- ============================================================== -->
<!-- Goroutine → Channels (horizontal, blue→teal) -->
<path fill="none" stroke-width="1.8" opacity="0.75" stroke="url(#gc-blue-teal)" d="M372,169 C398,169 412,169 438,169" marker-end="url(#arr-teal)"/>
<!-- Goroutine → Context (vertical within Foundation, 20px gap) -->
<path fill="none" stroke-width="1.8" opacity="0.75" stroke="#60a5fa" d="M212,208 C212,214 212,222 212,228" marker-end="url(#arr-blue)"/>
<!-- Channels → Buffered Channels (within comm, 20px gap) -->
<path fill="none" stroke-width="1.8" opacity="0.75" stroke="#2dd4bf" d="M598,208 C598,214 598,222 598,228" marker-end="url(#arr-teal)"/>
<!-- Channels → Select (within comm, offset right, longer path) -->
<path fill="none" stroke-width="1.8" opacity="0.75" stroke="#2dd4bf" d="M660,208 C660,254 660,280 660,326" marker-end="url(#arr-teal)"/>
<!-- Context → Select (blue→teal, cross-zone, dashed) -->
<path fill="none" stroke-width="1.8" opacity="0.75" stroke-dasharray="6 4" stroke="url(#gc-blue-teal-diag)" d="M372,267 C396,267 418,340 438,360" marker-end="url(#arr-teal)"/>
<!-- Goroutine → WaitGroup (blue→amber, vertical cross-zone) -->
<path fill="none" stroke-width="1.8" opacity="0.75" stroke="url(#gc-blue-amber-v)" d="M140,208 C140,320 140,420 140,476" marker-end="url(#arr-amber)"/>
<!-- Goroutine → Fan-Out (blue→rose, dashed cross-zone) -->
<path fill="none" stroke-width="1.8" opacity="0.75" stroke-dasharray="6 4" stroke="url(#gc-blue-rose)" d="M372,150 C398,150 418,490 438,515" marker-end="url(#arr-rose)"/>
<!-- Channels → Fan-Out (teal→rose, vertical cross-zone) -->
<path fill="none" stroke-width="1.8" opacity="0.75" stroke="url(#gc-teal-rose-v)" d="M530,208 C530,320 530,420 530,476" marker-end="url(#arr-rose)"/>
<!-- Atomic → Mutex (blue→amber, dashed vertical cross-zone) -->
<path fill="none" stroke-width="1.8" opacity="0.75" stroke-dasharray="6 4" stroke="url(#gc-amber-blue-v)" d="M212,404 C212,440 212,520 212,574" marker-end="url(#arr-amber)"/>
<!-- WaitGroup → Worker Pool (amber→rose, horizontal cross-zone) -->
<path fill="none" stroke-width="1.8" opacity="0.75" stroke="url(#gc-amber-rose)" d="M372,515 C396,515 418,595 438,613" marker-end="url(#arr-amber)"/>
<!-- Mutex → sync.Cond (within sync, 20px gap) -->
<path fill="none" stroke-width="1.8" opacity="0.75" stroke="#fbbf24" d="M212,652 C212,658 212,666 212,672" marker-end="url(#arr-amber)"/>
<!-- Fan-Out → Worker Pool (within patterns, 20px gap) -->
<path fill="none" stroke-width="1.8" opacity="0.75" stroke="#f472b6" d="M598,554 C598,560 598,568 598,574" marker-end="url(#arr-rose)"/>
<!-- Worker Pool → Pipeline (within patterns, 20px gap) -->
<path fill="none" stroke-width="1.8" opacity="0.75" stroke="#f472b6" d="M598,652 C598,658 598,666 598,672" marker-end="url(#arr-rose)"/>
<!-- ============================================================== -->
<!-- RIGHT PANEL -->
<!-- ============================================================== -->
<g transform="translate(820, 92)">
<rect width="348" height="666" rx="14" fill="url(#g-panel)" stroke="#334155" stroke-width="1"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px" x="24" y="32" fill="#94a3b8">How It Fits Together</text>
<g transform="translate(30, 56)">
<rect x="0" y="0" width="106" height="34" rx="6" fill="#1e3a5f" stroke="#3b82f6" stroke-width="1"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" x="53" y="17" text-anchor="middle" dominant-baseline="central" fill="#60a5fa">Goroutine</text>
<path d="M110,17 L148,17" stroke="#60a5fa" stroke-width="1.5" fill="none" marker-end="url(#arr-blue)"/>
<rect x="153" y="0" width="96" height="34" rx="6" fill="#134e4a" stroke="#14b8a6" stroke-width="1"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" x="201" y="17" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf">Channel</text>
<path d="M201,34 L201,60" stroke="#2dd4bf" stroke-width="1.5" fill="none" marker-end="url(#arr-teal)"/>
<rect x="153" y="64" width="96" height="34" rx="6" fill="#134e4a" stroke="#14b8a6" stroke-width="1"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" x="201" y="81" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf">Select</text>
<path d="M53,34 L53,60" stroke="#fbbf24" stroke-width="1.5" fill="none" marker-end="url(#arr-amber)"/>
<rect x="0" y="64" width="106" height="34" rx="6" fill="#451a03" stroke="#f59e0b" stroke-width="1"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" x="53" y="81" text-anchor="middle" dominant-baseline="central" fill="#fbbf24">WaitGroup</text>
<path d="M110,81 L148,81" stroke="#f472b6" stroke-width="1.5" fill="none" marker-end="url(#arr-rose)"/>
<path d="M201,98 L201,124" stroke="#f472b6" stroke-width="1.5" fill="none" marker-end="url(#arr-rose)"/>
<rect x="153" y="128" width="96" height="34" rx="6" fill="#4a0d2e" stroke="#ec4899" stroke-width="1"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" x="201" y="145" text-anchor="middle" dominant-baseline="central" fill="#f472b6">Worker Pool</text>
</g>
<!-- Legend -->
<g transform="translate(24, 248)">
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px" x="0" y="0" fill="#94a3b8">Legend</text>
<line x1="0" y1="26" x2="40" y2="26" stroke="#60a5fa" stroke-width="2"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#94a3b8" x="52" y="30">Foundation flow</text>
<line x1="0" y1="52" x2="40" y2="52" stroke="#2dd4bf" stroke-width="2"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#94a3b8" x="52" y="56">Communication flow</text>
<line x1="0" y1="78" x2="40" y2="78" stroke="#fbbf24" stroke-width="2"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#94a3b8" x="52" y="82">Synchronization flow</text>
<line x1="0" y1="104" x2="40" y2="104" stroke="#f472b6" stroke-width="2"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#94a3b8" x="52" y="108">Pattern composition</text>
<line x1="0" y1="134" x2="40" y2="134" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="6 4"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#94a3b8" x="52" y="138">Indirect / optional</text>
</g>
<!-- Key Principle -->
<g transform="translate(24, 420)">
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px" x="0" y="0" fill="#94a3b8">Key Principle</text>
<rect x="-4" y="16" width="308" height="76" rx="8" fill="none" stroke="#334155" stroke-width="0.5"/>
<rect x="-4" y="16" width="4" height="76" rx="2" fill="url(#g-accent)"/>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="12px" font-weight="600" fill="#f1f5f9" x="16" y="40">"Don't communicate by sharing</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="12px" font-weight="600" fill="#f1f5f9" x="16" y="58">memory; share memory by</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="12px" font-weight="600" fill="#f1f5f9" x="16" y="76">communicating."</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b" x="0" y="116">Channels (CSP model) are preferred</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b" x="0" y="133">over mutexes for most concurrency</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b" x="0" y="150">coordination in idiomatic Go.</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b" x="0" y="178">Goroutines are cheap (~2KB stack).</text>
<text font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" fill="#64748b" x="0" y="195">Spawn thousands — don't pool threads.</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 36 KiB

546
testdata/svg/patterns.svg vendored Normal file
View file

@ -0,0 +1,546 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 700">
<defs>
<!-- filters -->
<filter id="shadow" x="-6%" y="-6%" width="112%" height="116%">
<feDropShadow dx="1" dy="2" stdDeviation="5" flood-color="#000" flood-opacity="0.35"/>
</filter>
<filter id="title-glow" x="-10%" y="-10%" width="120%" height="120%">
<feGaussianBlur in="SourceAlpha" stdDeviation="8" result="blur"/>
<feFlood flood-color="#a78bfa" flood-opacity="0.25" result="color"/>
<feComposite in="color" in2="blur" operator="in" result="glow"/>
<feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<!-- arrow markers -->
<marker id="arr-teal" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#2dd4bf"/>
</marker>
<marker id="arr-blue" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#60a5fa"/>
</marker>
<marker id="arr-amber" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#fbbf24"/>
</marker>
<marker id="arr-violet" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#a78bfa"/>
</marker>
<marker id="arr-rose" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#f472b6"/>
</marker>
<!-- data ball gradients -->
<radialGradient id="g-ball-teal" cx="0.4" cy="0.35" r="0.6">
<stop offset="0%" stop-color="#5eead4"/><stop offset="100%" stop-color="#0d9488"/>
</radialGradient>
<radialGradient id="g-ball-blue" cx="0.4" cy="0.35" r="0.6">
<stop offset="0%" stop-color="#93c5fd"/><stop offset="100%" stop-color="#2563eb"/>
</radialGradient>
<radialGradient id="g-ball-violet" cx="0.4" cy="0.35" r="0.6">
<stop offset="0%" stop-color="#c4b5fd"/><stop offset="100%" stop-color="#7c3aed"/>
</radialGradient>
<radialGradient id="g-ball-amber" cx="0.4" cy="0.35" r="0.6">
<stop offset="0%" stop-color="#fde68a"/><stop offset="100%" stop-color="#d97706"/>
</radialGradient>
<radialGradient id="g-ball-rose" cx="0.4" cy="0.35" r="0.6">
<stop offset="0%" stop-color="#fda4af"/><stop offset="100%" stop-color="#be123c"/>
</radialGradient>
<!-- pipe interior gradients -->
<linearGradient id="gp-teal" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#14b8a6" stop-opacity="0.12"/><stop offset="100%" stop-color="#14b8a6" stop-opacity="0.04"/>
</linearGradient>
<linearGradient id="gp-blue" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#3b82f6" stop-opacity="0.12"/><stop offset="100%" stop-color="#3b82f6" stop-opacity="0.04"/>
</linearGradient>
<linearGradient id="gp-violet" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#8b5cf6" stop-opacity="0.12"/><stop offset="100%" stop-color="#8b5cf6" stop-opacity="0.04"/>
</linearGradient>
<linearGradient id="gp-amber" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#f59e0b" stop-opacity="0.12"/><stop offset="100%" stop-color="#f59e0b" stop-opacity="0.04"/>
</linearGradient>
<!-- ambient background glows -->
<radialGradient id="glow-top" cx="400" cy="160" r="300" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#8b5cf6" stop-opacity="0.04"/><stop offset="100%" stop-color="#8b5cf6" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-mid" cx="400" cy="380" r="300" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#14b8a6" stop-opacity="0.04"/><stop offset="100%" stop-color="#14b8a6" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-bot" cx="400" cy="580" r="300" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#f59e0b" stop-opacity="0.03"/><stop offset="100%" stop-color="#f59e0b" stop-opacity="0"/>
</radialGradient>
<!-- right panel -->
<linearGradient id="g-panel" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#141d2e" stop-opacity="0.9"/><stop offset="100%" stop-color="#0b1120" stop-opacity="0.95"/>
</linearGradient>
<!-- accent stripe -->
<linearGradient id="g-accent" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#8b5cf6"/><stop offset="25%" stop-color="#14b8a6"/>
<stop offset="50%" stop-color="#3b82f6"/><stop offset="75%" stop-color="#f59e0b"/>
<stop offset="100%" stop-color="#ec4899"/>
</linearGradient>
<!-- section separators -->
<linearGradient id="g-sep-teal" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#2dd4bf" stop-opacity="0"/><stop offset="15%" stop-color="#2dd4bf" stop-opacity="0.25"/>
<stop offset="85%" stop-color="#2dd4bf" stop-opacity="0.25"/><stop offset="100%" stop-color="#2dd4bf" stop-opacity="0"/>
</linearGradient>
<linearGradient id="g-sep-blue" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#60a5fa" stop-opacity="0"/><stop offset="15%" stop-color="#60a5fa" stop-opacity="0.25"/>
<stop offset="85%" stop-color="#60a5fa" stop-opacity="0.25"/><stop offset="100%" stop-color="#60a5fa" stop-opacity="0"/>
</linearGradient>
<linearGradient id="g-sep-amber" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#fbbf24" stop-opacity="0"/><stop offset="15%" stop-color="#fbbf24" stop-opacity="0.25"/>
<stop offset="85%" stop-color="#fbbf24" stop-opacity="0.25"/><stop offset="100%" stop-color="#fbbf24" stop-opacity="0"/>
</linearGradient>
</defs>
<style>
.bg { fill: #0f172a; }
.title { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 26px; font-weight: 700; fill: #f1f5f9; }
.subtitle { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 12px; font-weight: 400; fill: #94a3b8; letter-spacing: 2px; }
.section-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 600; letter-spacing: 1.5px; text-transform: uppercase; }
.pipe-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 12px; font-weight: 700; fill: #f1f5f9; }
.pipe-desc { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10px; font-weight: 400; fill: #94a3b8; }
.code { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 10px; fill: #e2e8f0; }
.code-sm { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 9px; fill: #e2e8f0; }
.code-amber { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 9px; fill: #fbbf24; }
.g-label { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 9px; font-weight: 700; }
.mini-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10px; font-weight: 600; }
.group-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10px; font-weight: 600; letter-spacing: 1.5px; text-transform: uppercase; }
.quote { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 600; fill: #f1f5f9; }
.quote-note { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10px; fill: #64748b; }
.legend-text { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10px; fill: #94a3b8; }
.desc-text { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10px; fill: #cbd5e1; }
</style>
<!-- background -->
<g opacity="0.05">
<pattern id="dots" width="40" height="40" patternUnits="userSpaceOnUse">
<circle cx="20" cy="20" r="0.8" fill="#94a3b8"/>
</pattern>
<rect width="1200" height="700" fill="url(#dots)"/>
</g>
<!-- ambient glows -->
<rect width="1200" height="700" fill="url(#glow-top)"/>
<rect width="1200" height="700" fill="url(#glow-mid)"/>
<rect width="1200" height="700" fill="url(#glow-bot)"/>
<!-- ================================================================ -->
<!-- TITLE -->
<!-- ================================================================ -->
<g filter="url(#title-glow)">
<text class="title" x="420" y="38" text-anchor="middle" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="26px" font-weight="700" fill="#f1f5f9">Go Concurrency Patterns</text>
</g>
<text class="subtitle" x="420" y="58" text-anchor="middle" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="12px" font-weight="400" fill="#94a3b8" letter-spacing="2px">fan-out · fan-in · pipeline · worker pool · or-done</text>
<!-- ================================================================ -->
<!-- PATTERN 1: FAN-OUT / FAN-IN (y=90230) -->
<!-- ================================================================ -->
<text class="section-label" x="40" y="102" fill="#a78bfa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">Fan-Out / Fan-In</text>
<g transform="translate(40, 118)">
<!-- INPUT PIPE -->
<g filter="url(#shadow)">
<rect x="0" y="40" width="120" height="14" rx="7" fill="url(#gp-violet)" stroke="#8b5cf6" stroke-width="1.5"/>
<!-- flow chevrons -->
<path d="M20,44 l4,3 l-4,3" stroke="#a78bfa" stroke-width="0.8" fill="none" opacity="0.3"/>
<path d="M50,44 l4,3 l-4,3" stroke="#a78bfa" stroke-width="0.8" fill="none" opacity="0.3"/>
<path d="M80,44 l4,3 l-4,3" stroke="#a78bfa" stroke-width="0.8" fill="none" opacity="0.3"/>
<!-- data balls entering -->
<circle cx="30" cy="47" r="4" fill="url(#g-ball-violet)"/>
<circle cx="70" cy="47" r="4" fill="url(#g-ball-violet)" opacity="0.7"/>
<circle cx="100" cy="47" r="4" fill="url(#g-ball-violet)" opacity="0.5"/>
</g>
<text class="pipe-desc" x="40" y="36" text-anchor="middle" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="400" fill="#94a3b8">input ch</text>
<!-- FAN-OUT JUNCTION: bezier T-split -->
<path d="M120,47 C150,47 155,15 185,15" stroke="#8b5cf6" stroke-width="1.5" fill="none"/>
<path d="M120,47 C150,47 155,47 185,47" stroke="#8b5cf6" stroke-width="1.5" fill="none"/>
<path d="M120,47 C150,47 155,79 185,79" stroke="#8b5cf6" stroke-width="1.5" fill="none"/>
<!-- WORKER 1 pipe segment + G-badge -->
<g filter="url(#shadow)">
<rect x="185" y="8" width="80" height="14" rx="7" fill="url(#gp-violet)" stroke="#8b5cf6" stroke-width="1.2"/>
<circle cx="210" cy="15" r="4" fill="url(#g-ball-violet)" opacity="0.6"/>
</g>
<g filter="url(#shadow)">
<circle cx="290" cy="15" r="13" fill="#271650" stroke="#8b5cf6" stroke-width="1.5"/>
<text class="g-label" x="290" y="15" text-anchor="middle" dominant-baseline="central" fill="#a78bfa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">W1</text>
</g>
<text class="pipe-desc" x="290" y="1" text-anchor="middle" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="400" fill="#94a3b8">process</text>
<!-- WORKER 2 pipe segment + G-badge -->
<g filter="url(#shadow)">
<rect x="185" y="40" width="80" height="14" rx="7" fill="url(#gp-violet)" stroke="#8b5cf6" stroke-width="1.2"/>
<circle cx="230" cy="47" r="4" fill="url(#g-ball-violet)"/>
</g>
<g filter="url(#shadow)">
<circle cx="290" cy="47" r="13" fill="#271650" stroke="#8b5cf6" stroke-width="1.5"/>
<text class="g-label" x="290" y="47" text-anchor="middle" dominant-baseline="central" fill="#a78bfa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">W2</text>
</g>
<text class="pipe-desc" x="290" y="33" text-anchor="middle" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="400" fill="#94a3b8">process</text>
<!-- WORKER 3 pipe segment + G-badge -->
<g filter="url(#shadow)">
<rect x="185" y="72" width="80" height="14" rx="7" fill="url(#gp-violet)" stroke="#8b5cf6" stroke-width="1.2"/>
<circle cx="245" cy="79" r="4" fill="url(#g-ball-violet)" opacity="0.8"/>
</g>
<g filter="url(#shadow)">
<circle cx="290" cy="79" r="13" fill="#271650" stroke="#8b5cf6" stroke-width="1.5"/>
<text class="g-label" x="290" y="79" text-anchor="middle" dominant-baseline="central" fill="#a78bfa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">W3</text>
</g>
<text class="pipe-desc" x="290" y="65" text-anchor="middle" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="400" fill="#94a3b8">process</text>
<!-- Worker output pipes -->
<g filter="url(#shadow)">
<rect x="310" y="8" width="80" height="14" rx="7" fill="url(#gp-violet)" stroke="#8b5cf6" stroke-width="1.2"/>
<circle cx="350" cy="15" r="4" fill="url(#g-ball-violet)" opacity="0.8"/>
</g>
<g filter="url(#shadow)">
<rect x="310" y="40" width="80" height="14" rx="7" fill="url(#gp-violet)" stroke="#8b5cf6" stroke-width="1.2"/>
<circle cx="340" cy="47" r="4" fill="url(#g-ball-violet)" opacity="0.5"/>
</g>
<g filter="url(#shadow)">
<rect x="310" y="72" width="80" height="14" rx="7" fill="url(#gp-violet)" stroke="#8b5cf6" stroke-width="1.2"/>
<circle cx="360" cy="79" r="4" fill="url(#g-ball-violet)" opacity="0.9"/>
</g>
<!-- FAN-IN JUNCTION: bezier Y-merge -->
<path d="M390,15 C420,15 425,47 455,47" stroke="#8b5cf6" stroke-width="1.5" fill="none"/>
<path d="M390,47 C420,47 425,47 455,47" stroke="#8b5cf6" stroke-width="1.5" fill="none"/>
<path d="M390,79 C420,79 425,47 455,47" stroke="#8b5cf6" stroke-width="1.5" fill="none"/>
<!-- OUTPUT PIPE -->
<g filter="url(#shadow)">
<rect x="455" y="40" width="120" height="14" rx="7" fill="url(#gp-violet)" stroke="#8b5cf6" stroke-width="1.5"/>
<path d="M480,44 l4,3 l-4,3" stroke="#a78bfa" stroke-width="0.8" fill="none" opacity="0.3"/>
<path d="M510,44 l4,3 l-4,3" stroke="#a78bfa" stroke-width="0.8" fill="none" opacity="0.3"/>
<path d="M540,44 l4,3 l-4,3" stroke="#a78bfa" stroke-width="0.8" fill="none" opacity="0.3"/>
<circle cx="490" cy="47" r="4" fill="url(#g-ball-violet)"/>
<circle cx="530" cy="47" r="4" fill="url(#g-ball-violet)" opacity="0.6"/>
</g>
<text class="pipe-desc" x="515" y="36" text-anchor="middle" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="400" fill="#94a3b8">output ch</text>
<!-- junction labels -->
<text class="pipe-desc" x="140" y="95" fill="#64748b" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="400">fan-out</text>
<text class="pipe-desc" x="420" y="95" fill="#64748b" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="400">fan-in</text>
</g>
<!-- separator -->
<rect x="40" y="234" width="790" height="1" fill="url(#g-sep-teal)"/>
<!-- ================================================================ -->
<!-- PATTERN 2: PIPELINE (y=250380) -->
<!-- ================================================================ -->
<text class="section-label" x="40" y="260" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">Pipeline</text>
<g transform="translate(40, 274)">
<!-- STAGE 1: generate -->
<g filter="url(#shadow)">
<rect x="0" y="14" width="90" height="36" rx="8" fill="#0d3331" stroke="#14b8a6" stroke-width="1.5"/>
<text class="mini-label" x="45" y="32" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="600">generate</text>
</g>
<!-- G-badge on stage -->
<circle cx="80" cy="14" r="8" fill="#134e4a" stroke="#14b8a6" stroke-width="1"/>
<text class="g-label" x="80" y="14" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-size="7" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-weight="700">G</text>
<!-- PIPE 1 -->
<g filter="url(#shadow)">
<rect x="100" y="26" width="70" height="12" rx="6" fill="url(#gp-teal)" stroke="#14b8a6" stroke-width="1.2"/>
<path d="M115,30 l3,2 l-3,2" stroke="#2dd4bf" stroke-width="0.7" fill="none" opacity="0.4"/>
<path d="M135,30 l3,2 l-3,2" stroke="#2dd4bf" stroke-width="0.7" fill="none" opacity="0.4"/>
<path d="M155,30 l3,2 l-3,2" stroke="#2dd4bf" stroke-width="0.7" fill="none" opacity="0.4"/>
<circle cx="145" cy="32" r="3.5" fill="url(#g-ball-teal)"/>
</g>
<text class="code-amber" x="135" y="22" text-anchor="middle" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" fill="#fbbf24">&lt;-chan int</text>
<!-- STAGE 2: transform -->
<g filter="url(#shadow)">
<rect x="180" y="14" width="90" height="36" rx="8" fill="#0d3331" stroke="#14b8a6" stroke-width="1.5"/>
<text class="mini-label" x="225" y="32" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="600">transform</text>
</g>
<circle cx="260" cy="14" r="8" fill="#134e4a" stroke="#14b8a6" stroke-width="1"/>
<text class="g-label" x="260" y="14" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-size="7" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-weight="700">G</text>
<!-- PIPE 2 -->
<g filter="url(#shadow)">
<rect x="280" y="26" width="70" height="12" rx="6" fill="url(#gp-teal)" stroke="#14b8a6" stroke-width="1.2"/>
<path d="M295,30 l3,2 l-3,2" stroke="#2dd4bf" stroke-width="0.7" fill="none" opacity="0.4"/>
<path d="M315,30 l3,2 l-3,2" stroke="#2dd4bf" stroke-width="0.7" fill="none" opacity="0.4"/>
<path d="M335,30 l3,2 l-3,2" stroke="#2dd4bf" stroke-width="0.7" fill="none" opacity="0.4"/>
<circle cx="310" cy="32" r="3.5" fill="url(#g-ball-teal)"/>
<circle cx="330" cy="32" r="2.5" fill="url(#g-ball-teal)" opacity="0.5"/>
</g>
<text class="code-amber" x="315" y="22" text-anchor="middle" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" fill="#fbbf24">&lt;-chan int</text>
<!-- STAGE 3: filter -->
<g filter="url(#shadow)">
<rect x="360" y="14" width="90" height="36" rx="8" fill="#0d3331" stroke="#14b8a6" stroke-width="1.5"/>
<text class="mini-label" x="405" y="32" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="600">filter</text>
</g>
<circle cx="440" cy="14" r="8" fill="#134e4a" stroke="#14b8a6" stroke-width="1"/>
<text class="g-label" x="440" y="14" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-size="7" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-weight="700">G</text>
<!-- PIPE 3 -->
<g filter="url(#shadow)">
<rect x="460" y="26" width="70" height="12" rx="6" fill="url(#gp-teal)" stroke="#14b8a6" stroke-width="1.2"/>
<path d="M475,30 l3,2 l-3,2" stroke="#2dd4bf" stroke-width="0.7" fill="none" opacity="0.4"/>
<path d="M495,30 l3,2 l-3,2" stroke="#2dd4bf" stroke-width="0.7" fill="none" opacity="0.4"/>
<path d="M515,30 l3,2 l-3,2" stroke="#2dd4bf" stroke-width="0.7" fill="none" opacity="0.4"/>
<circle cx="500" cy="32" r="2.5" fill="url(#g-ball-teal)" opacity="0.4"/>
</g>
<text class="code-amber" x="495" y="22" text-anchor="middle" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" fill="#fbbf24">&lt;-chan int</text>
<!-- STAGE 4: sink -->
<g filter="url(#shadow)">
<rect x="540" y="14" width="70" height="36" rx="8" fill="#0d3331" stroke="#14b8a6" stroke-width="1.5"/>
<text class="mini-label" x="575" y="32" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="600">sink</text>
</g>
<circle cx="600" cy="14" r="8" fill="#134e4a" stroke="#14b8a6" stroke-width="1"/>
<text class="g-label" x="600" y="14" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-size="7" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-weight="700">G</text>
<!-- description -->
<text class="desc-text" x="0" y="72" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#cbd5e1">each stage runs in its own goroutine, connected by typed channels</text>
</g>
<!-- separator -->
<rect x="40" y="386" width="790" height="1" fill="url(#g-sep-blue)"/>
<!-- ================================================================ -->
<!-- PATTERN 3: WORKER POOL (y=400530) -->
<!-- ================================================================ -->
<text class="section-label" x="40" y="410" fill="#60a5fa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">Worker Pool</text>
<g transform="translate(40, 424)">
<!-- JOBS CHANNEL (pipe) -->
<g filter="url(#shadow)">
<rect x="0" y="26" width="140" height="14" rx="7" fill="url(#gp-blue)" stroke="#3b82f6" stroke-width="1.5"/>
<!-- queued data balls -->
<circle cx="25" cy="33" r="4" fill="url(#g-ball-blue)"/>
<circle cx="50" cy="33" r="4" fill="url(#g-ball-blue)" opacity="0.85"/>
<circle cx="75" cy="33" r="4" fill="url(#g-ball-blue)" opacity="0.7"/>
<circle cx="100" cy="33" r="4" fill="url(#g-ball-blue)" opacity="0.55"/>
<circle cx="125" cy="33" r="4" fill="url(#g-ball-blue)" opacity="0.4"/>
</g>
<text class="pipe-desc" x="70" y="22" text-anchor="middle" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="400" fill="#94a3b8">jobs ch</text>
<!-- Arrows from jobs to each worker -->
<path d="M140,33 C165,33 170,10 195,10" stroke="#3b82f6" stroke-width="1.2" fill="none" marker-end="url(#arr-blue)"/>
<path d="M140,33 C165,33 170,27 195,27" stroke="#3b82f6" stroke-width="1.2" fill="none" marker-end="url(#arr-blue)"/>
<path d="M140,33 C165,33 170,44 195,44" stroke="#3b82f6" stroke-width="1.2" fill="none" marker-end="url(#arr-blue)"/>
<path d="M140,33 C165,33 170,61 195,61" stroke="#3b82f6" stroke-width="1.2" fill="none" marker-end="url(#arr-blue)"/>
<!-- WORKER G-BADGES (vertical stack) -->
<!-- W1: active -->
<g filter="url(#shadow)">
<circle cx="214" cy="10" r="13" fill="#1e3a5f" stroke="#3b82f6" stroke-width="1.5"/>
<text class="g-label" x="214" y="10" text-anchor="middle" dominant-baseline="central" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">W1</text>
</g>
<!-- W2: active -->
<g filter="url(#shadow)">
<circle cx="214" cy="27" r="13" fill="#1e3a5f" stroke="#3b82f6" stroke-width="1.5"/>
<text class="g-label" x="214" y="27" text-anchor="middle" dominant-baseline="central" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">W2</text>
</g>
<!-- W3: active -->
<g filter="url(#shadow)">
<circle cx="214" cy="44" r="13" fill="#1e3a5f" stroke="#3b82f6" stroke-width="1.5"/>
<text class="g-label" x="214" y="44" text-anchor="middle" dominant-baseline="central" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">W3</text>
</g>
<!-- W4: idle (dashed) -->
<circle cx="214" cy="61" r="13" fill="#1e3a5f" stroke="#3b82f6" stroke-width="1.5" stroke-dasharray="4 3" opacity="0.5"/>
<text class="g-label" x="214" y="61" text-anchor="middle" dominant-baseline="central" fill="#60a5fa" opacity="0.5" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">W4</text>
<!-- WaitGroup badge -->
<g filter="url(#shadow)">
<rect x="196" y="79" width="36" height="16" rx="4" fill="#1e3a5f" stroke="#60a5fa" stroke-width="1"/>
<text class="code-sm" x="214" y="90" text-anchor="middle" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">wg:3/4</text>
</g>
<!-- Arrows from each worker to results -->
<path d="M232,10 C260,10 265,33 290,33" stroke="#3b82f6" stroke-width="1.2" fill="none" marker-end="url(#arr-blue)"/>
<path d="M232,27 C260,27 265,33 290,33" stroke="#3b82f6" stroke-width="1.2" fill="none" marker-end="url(#arr-blue)"/>
<path d="M232,44 C260,44 265,33 290,33" stroke="#3b82f6" stroke-width="1.2" fill="none" marker-end="url(#arr-blue)"/>
<path d="M232,61 C260,61 265,33 290,33" stroke="#3b82f6" stroke-width="1.2" fill="none" stroke-dasharray="4 3" opacity="0.4"/>
<!-- RESULTS CHANNEL (pipe) -->
<g filter="url(#shadow)">
<rect x="290" y="26" width="140" height="14" rx="7" fill="url(#gp-blue)" stroke="#3b82f6" stroke-width="1.5"/>
<path d="M310,30 l3,2 l-3,2" stroke="#60a5fa" stroke-width="0.7" fill="none" opacity="0.3"/>
<path d="M340,30 l3,2 l-3,2" stroke="#60a5fa" stroke-width="0.7" fill="none" opacity="0.3"/>
<path d="M370,30 l3,2 l-3,2" stroke="#60a5fa" stroke-width="0.7" fill="none" opacity="0.3"/>
<circle cx="330" cy="33" r="4" fill="url(#g-ball-blue)"/>
<circle cx="390" cy="33" r="4" fill="url(#g-ball-blue)" opacity="0.6"/>
</g>
<text class="pipe-desc" x="360" y="22" text-anchor="middle" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="400" fill="#94a3b8">results ch</text>
<!-- code label -->
<text class="code" x="140" y="104" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px">for job := range jobs</text>
</g>
<!-- separator -->
<rect x="40" y="540" width="790" height="1" fill="url(#g-sep-amber)"/>
<!-- ================================================================ -->
<!-- PATTERN 4: OR-DONE (y=550670) -->
<!-- ================================================================ -->
<text class="section-label" x="40" y="562" fill="#fbbf24" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">Or-Done</text>
<g transform="translate(40, 574)">
<!-- INPUT CHANNEL (potentially slow/stuck) -->
<g filter="url(#shadow)">
<rect x="0" y="24" width="100" height="14" rx="7" fill="url(#gp-amber)" stroke="#f59e0b" stroke-width="1.5"/>
<path d="M20,28 l3,2 l-3,2" stroke="#fbbf24" stroke-width="0.7" fill="none" opacity="0.3"/>
<path d="M50,28 l3,2 l-3,2" stroke="#fbbf24" stroke-width="0.7" fill="none" opacity="0.3"/>
<circle cx="40" cy="31" r="3.5" fill="url(#g-ball-amber)"/>
</g>
<text class="pipe-desc" x="50" y="20" text-anchor="middle" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="400" fill="#94a3b8">input ch</text>
<text class="pipe-desc" x="50" y="50" text-anchor="middle" fill="#64748b" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="400">may block</text>
<!-- Arrow into or-done wrapper -->
<path d="M100,31 L130,31" stroke="#f59e0b" stroke-width="1.2" fill="none" marker-end="url(#arr-amber)"/>
<!-- OR-DONE WRAPPER BLOCK -->
<g filter="url(#shadow)">
<rect x="138" y="0" width="180" height="62" rx="10" fill="none" stroke="#f59e0b" stroke-width="1.5" stroke-dasharray="6 3"/>
<text class="mini-label" x="228" y="12" text-anchor="middle" fill="#fbbf24" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="600">or-done</text>
</g>
<!-- Select diamond inside -->
<polygon points="228,25 248,38 228,51 208,38" fill="#1e1a0b" stroke="#f59e0b" stroke-width="1.2"/>
<text class="code-sm" x="228" y="40" text-anchor="middle" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">sel</text>
<!-- Case 1: <-ch (data path, teal arrow) -->
<path d="M248,32 L310,32" stroke="#14b8a6" stroke-width="1.5" fill="none" marker-end="url(#arr-teal)"/>
<text class="code-sm" x="275" y="26" text-anchor="middle" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">&lt;-ch</text>
<!-- Case 2: <-ctx.Done() (cancel path, rose arrow) -->
<path d="M248,44 L310,44" stroke="#ec4899" stroke-width="1.5" fill="none" marker-end="url(#arr-rose)"/>
<text class="code-sm" x="275" y="56" text-anchor="middle" fill="#f472b6" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">&lt;-ctx.Done()</text>
<!-- OUTPUT CHANNEL (guaranteed cancellation-safe) -->
<g filter="url(#shadow)">
<rect x="328" y="24" width="110" height="14" rx="7" fill="url(#gp-teal)" stroke="#14b8a6" stroke-width="1.5"/>
<path d="M348,28 l3,2 l-3,2" stroke="#2dd4bf" stroke-width="0.7" fill="none" opacity="0.3"/>
<path d="M378,28 l3,2 l-3,2" stroke="#2dd4bf" stroke-width="0.7" fill="none" opacity="0.3"/>
<circle cx="360" cy="31" r="3.5" fill="url(#g-ball-teal)"/>
</g>
<text class="pipe-desc" x="383" y="20" text-anchor="middle" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="400" fill="#94a3b8">output ch</text>
<!-- Cancel scenario: rose X when ctx fires -->
<g transform="translate(340, 48)">
<line x1="-4" y1="-4" x2="4" y2="4" stroke="#f472b6" stroke-width="2" stroke-linecap="round"/>
<line x1="4" y1="-4" x2="-4" y2="4" stroke="#f472b6" stroke-width="2" stroke-linecap="round"/>
<text class="pipe-desc" x="12" y="4" fill="#f472b6" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="400">exit</text>
</g>
<!-- description -->
<text class="desc-text" x="0" y="82" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#cbd5e1">wraps any channel to respect cancellation</text>
</g>
<!-- ================================================================ -->
<!-- RIGHT PANEL -->
<!-- ================================================================ -->
<g transform="translate(860, 88)">
<rect width="310" height="588" rx="14" fill="url(#g-panel)" stroke="#334155" stroke-width="1"/>
<!-- ── When to Use ─────────────────────────────────────────────── -->
<text class="group-label" x="24" y="28" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="600" letter-spacing="1.5px">When to Use</text>
<g transform="translate(24, 46)">
<!-- violet bullet: Fan-Out/In -->
<circle cx="6" cy="4" r="4" fill="#8b5cf6"/>
<text class="legend-text" x="18" y="8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#94a3b8">CPU-bound parallel work</text>
<text class="code-sm" x="220" y="8" fill="#a78bfa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">Fan-Out/In</text>
<!-- teal bullet: Pipeline -->
<circle cx="6" cy="26" r="4" fill="#14b8a6"/>
<text class="legend-text" x="18" y="30" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#94a3b8">Stream processing</text>
<text class="code-sm" x="220" y="30" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">Pipeline</text>
<!-- blue bullet: Worker Pool -->
<circle cx="6" cy="48" r="4" fill="#3b82f6"/>
<text class="legend-text" x="18" y="52" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#94a3b8">Bounded I/O work</text>
<text class="code-sm" x="220" y="52" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">Worker Pool</text>
<!-- amber bullet: Or-Done -->
<circle cx="6" cy="70" r="4" fill="#f59e0b"/>
<text class="legend-text" x="18" y="74" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#94a3b8">Untrusted channels</text>
<text class="code-sm" x="220" y="74" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">Or-Done</text>
</g>
<!-- ── Composition ─────────────────────────────────────────────── -->
<text class="group-label" x="24" y="154" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="600" letter-spacing="1.5px">Composition</text>
<g transform="translate(24, 170)">
<text class="quote-note" x="0" y="0" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#64748b">These patterns compose: a pipeline</text>
<text class="quote-note" x="0" y="15" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#64748b">stage can internally use a worker</text>
<text class="quote-note" x="0" y="30" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#64748b">pool, and each worker can use</text>
<text class="quote-note" x="0" y="45" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#64748b">or-done to wrap external channels.</text>
<!-- mini composition diagram -->
<g transform="translate(20, 65)">
<!-- pipeline box -->
<rect x="0" y="0" width="220" height="40" rx="6" fill="none" stroke="#14b8a6" stroke-width="0.8" stroke-dasharray="4 2" opacity="0.5"/>
<text class="code-sm" x="8" y="12" fill="#2dd4bf" opacity="0.7" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">pipeline stage</text>
<!-- worker pool box inside -->
<rect x="8" y="18" width="100" height="18" rx="4" fill="none" stroke="#3b82f6" stroke-width="0.8" stroke-dasharray="3 2" opacity="0.5"/>
<text class="code-sm" x="16" y="30" fill="#60a5fa" opacity="0.7" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">worker pool</text>
<!-- or-done box inside -->
<rect x="118" y="18" width="92" height="18" rx="4" fill="none" stroke="#f59e0b" stroke-width="0.8" stroke-dasharray="3 2" opacity="0.5"/>
<text class="code-sm" x="126" y="30" fill="#fbbf24" opacity="0.7" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">or-done</text>
</g>
</g>
<!-- ── Key Insight ─────────────────────────────────────────────── -->
<text class="group-label" x="24" y="320" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="600" letter-spacing="1.5px">Key Insight</text>
<g transform="translate(24, 336)">
<rect x="-4" y="0" width="270" height="58" rx="8" fill="none" stroke="#334155" stroke-width="0.5"/>
<rect x="-4" y="0" width="4" height="58" rx="2" fill="url(#g-accent)"/>
<text class="quote" x="14" y="20" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" fill="#f1f5f9">Every pattern follows the same</text>
<text class="quote" x="14" y="36" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" fill="#f1f5f9">shape: goroutines connected by</text>
<text class="quote" x="14" y="52" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" fill="#f1f5f9">channels, with clear ownership</text>
</g>
<g transform="translate(24, 402)">
<text class="quote-note" x="0" y="0" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#64748b">of who closes what.</text>
</g>
<!-- ── Visual Key ──────────────────────────────────────────────── -->
<text class="group-label" x="24" y="434" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="600" letter-spacing="1.5px">Visual Key</text>
<g transform="translate(24, 450)">
<!-- channel pipe -->
<rect x="0" y="0" width="40" height="10" rx="5" fill="url(#gp-teal)" stroke="#14b8a6" stroke-width="1.2"/>
<text class="legend-text" x="52" y="9" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#94a3b8">Channel (pipe)</text>
<!-- data ball -->
<circle cx="20" cy="26" r="4" fill="url(#g-ball-teal)"/>
<text class="legend-text" x="52" y="30" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#94a3b8">Data value in transit</text>
<!-- G-badge active -->
<circle cx="20" cy="48" r="8" fill="#134e4a" stroke="#14b8a6" stroke-width="1"/>
<text class="g-label" x="20" y="48" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-size="7" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-weight="700">G</text>
<text class="legend-text" x="52" y="52" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#94a3b8">Active goroutine</text>
<!-- G-badge idle -->
<circle cx="20" cy="70" r="8" fill="#1e3a5f" stroke="#3b82f6" stroke-width="1" stroke-dasharray="3 2" opacity="0.5"/>
<text class="g-label" x="20" y="70" text-anchor="middle" dominant-baseline="central" fill="#60a5fa" font-size="7" opacity="0.5" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-weight="700">G</text>
<text class="legend-text" x="52" y="74" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#94a3b8">Idle goroutine</text>
<!-- select diamond -->
<polygon points="20,82 28,90 20,98 12,90" fill="none" stroke="#f59e0b" stroke-width="1"/>
<text class="legend-text" x="52" y="94" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#94a3b8">Select statement</text>
<!-- flow chevron -->
<path d="M14,110 l6,4 l-6,4" stroke="#94a3b8" stroke-width="1.2" fill="none"/>
<text class="legend-text" x="52" y="116" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#94a3b8">Data flow direction</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 39 KiB

454
testdata/svg/race-conditions.svg vendored Normal file
View file

@ -0,0 +1,454 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 620">
<defs>
<!-- dot grid pattern -->
<pattern id="dots" width="40" height="40" patternUnits="userSpaceOnUse">
<circle cx="20" cy="20" r="0.8" fill="#94a3b8" opacity="0.05"/>
</pattern>
<!-- drop shadow -->
<filter id="shadow" x="-6%" y="-6%" width="112%" height="116%">
<feDropShadow dx="1" dy="2" stdDeviation="5" flood-color="#000" flood-opacity="0.35"/>
</filter>
<!-- title glow (rose for danger theme) -->
<filter id="title-glow" x="-10%" y="-10%" width="120%" height="120%">
<feGaussianBlur in="SourceAlpha" stdDeviation="8" result="blur"/>
<feFlood flood-color="#f472b6" flood-opacity="0.25" result="color"/>
<feComposite in="color" in2="blur" operator="in" result="glow"/>
<feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<!-- arrow markers -->
<marker id="arr-blue" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#60a5fa"/>
</marker>
<marker id="arr-rose" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#f472b6"/>
</marker>
<marker id="arr-teal" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#2dd4bf"/>
</marker>
<marker id="arr-amber" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#fbbf24"/>
</marker>
<!-- right panel gradient -->
<linearGradient id="g-panel" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#141d2e"/>
<stop offset="100%" stop-color="#0b1120"/>
</linearGradient>
<!-- collision starburst gradient -->
<radialGradient id="g-starburst" cx="0.5" cy="0.5" r="0.5">
<stop offset="0%" stop-color="#f472b6" stop-opacity="0.9"/>
<stop offset="60%" stop-color="#ec4899" stop-opacity="0.5"/>
<stop offset="100%" stop-color="#ec4899" stop-opacity="0"/>
</radialGradient>
<!-- safe glow -->
<radialGradient id="g-safe" cx="0.5" cy="0.5" r="0.5">
<stop offset="0%" stop-color="#2dd4bf" stop-opacity="0.3"/>
<stop offset="100%" stop-color="#14b8a6" stop-opacity="0"/>
</radialGradient>
<!-- ambient glows -->
<radialGradient id="glow-top" cx="300" cy="180" r="350" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#ec4899" stop-opacity="0.04"/>
<stop offset="100%" stop-color="#ec4899" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-bottom" cx="400" cy="440" r="400" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#f59e0b" stop-opacity="0.03"/>
<stop offset="100%" stop-color="#f59e0b" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-right" cx="1010" cy="340" r="250" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#14b8a6" stop-opacity="0.04"/>
<stop offset="100%" stop-color="#14b8a6" stop-opacity="0"/>
</radialGradient>
<!-- shared variable gradient (amber) -->
<linearGradient id="g-shared" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#1f1a0a"/>
<stop offset="100%" stop-color="#1a1508"/>
</linearGradient>
<!-- mutex gate gradient -->
<linearGradient id="g-mutex" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#3d2e0a"/>
<stop offset="100%" stop-color="#2a1f08"/>
</linearGradient>
</defs>
<!-- ====== BACKGROUND ====== -->
<rect width="1200" height="620" fill="url(#dots)"/>
<rect width="1200" height="620" fill="url(#glow-top)"/>
<rect width="1200" height="620" fill="url(#glow-bottom)"/>
<rect width="1200" height="620" fill="url(#glow-right)"/>
<!-- ====== TITLE ====== -->
<text x="600" y="38" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="26" font-weight="700" fill="#f1f5f9" filter="url(#title-glow)">Go Race Conditions</text>
<text x="600" y="62" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="12" fill="#f472b6" letter-spacing="3" font-weight="500">detection · common patterns · fixes</text>
<!-- ====== TOP SECTION: DATA RACE ANATOMY ====== -->
<text x="52" y="95" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="10" font-weight="700" fill="#f472b6" letter-spacing="2.5" opacity="0.9">DATA RACE ANATOMY</text>
<!-- ====== THE COLLISION (left) ====== -->
<g filter="url(#shadow)">
<!-- collision area background -->
<rect x="52" y="110" width="450" height="170" rx="10" fill="none" stroke="#1e293b" stroke-width="1" opacity="0.6"/>
</g>
<!-- shared variable in center -->
<rect x="235" y="170" width="100" height="36" rx="6" fill="url(#g-shared)" stroke="#f59e0b" stroke-width="1.5" stroke-opacity="0.6"/>
<text x="285" y="193" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="11" fill="#fbbf24">counter</text>
<!-- G1 badge (top-left) -->
<circle cx="110" cy="140" r="16" fill="none" stroke="#f472b6" stroke-width="1.5"/>
<text x="110" y="145" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="11" font-weight="700" fill="#f472b6">G1</text>
<!-- G1 path to shared variable (WRITE) -->
<line x1="126" y1="148" x2="232" y2="180" stroke="#f472b6" stroke-width="1.5" stroke-dasharray="4,3" marker-end="url(#arr-rose)"/>
<text x="160" y="155" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" font-weight="700" fill="#f472b6" letter-spacing="1">WRITE</text>
<!-- G2 badge (bottom-left) -->
<circle cx="110" cy="240" r="16" fill="none" stroke="#60a5fa" stroke-width="1.5"/>
<text x="110" y="245" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="11" font-weight="700" fill="#60a5fa">G2</text>
<!-- G2 path to shared variable (READ) -->
<line x1="126" y1="232" x2="232" y2="198" stroke="#60a5fa" stroke-width="1.5" stroke-dasharray="4,3" marker-end="url(#arr-blue)"/>
<text x="160" y="237" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" font-weight="700" fill="#60a5fa" letter-spacing="1">READ</text>
<!-- collision starburst -->
<circle cx="250" cy="188" r="22" fill="url(#g-starburst)"/>
<!-- starburst spikes -->
<g stroke="#f472b6" stroke-width="1.5" opacity="0.8">
<line x1="250" y1="164" x2="250" y2="156"/>
<line x1="266" y1="172" x2="272" y2="166"/>
<line x1="272" y1="188" x2="280" y2="188"/>
<line x1="266" y1="204" x2="272" y2="210"/>
<line x1="250" y1="212" x2="250" y2="220"/>
<line x1="234" y1="204" x2="228" y2="210"/>
<line x1="228" y1="188" x2="220" y2="188"/>
<line x1="234" y1="172" x2="228" y2="166"/>
</g>
<!-- RACE! label -->
<rect x="340" y="155" width="58" height="22" rx="4" fill="none" stroke="#f472b6" stroke-width="1"/>
<text x="369" y="170" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="11" font-weight="800" fill="#f472b6" letter-spacing="1">RACE!</text>
<!-- timing bars (overlapping = race) -->
<text x="340" y="200" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" fill="#94a3b8">same time:</text>
<rect x="395" y="192" width="80" height="7" rx="2" fill="#6b2048" opacity="0.8"/>
<text x="400" y="199" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="6" fill="#e2e8f0">G1 write</text>
<rect x="415" y="203" width="80" height="7" rx="2" fill="#1e3a5f" opacity="0.8"/>
<text x="420" y="210" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="6" fill="#e2e8f0">G2 read</text>
<!-- overlap indicator -->
<rect x="415" y="192" width="60" height="18" rx="2" fill="none" stroke="#f472b6" stroke-width="0.5" stroke-dasharray="2,2"/>
<text x="445" y="225" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="7" fill="#f472b6">overlap = danger</text>
<!-- code snippet below collision -->
<text x="80" y="264" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#64748b">G1:</text>
<text x="105" y="264" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#e2e8f0">counter++</text>
<text x="200" y="264" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#64748b">G2:</text>
<text x="225" y="264" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#e2e8f0">fmt.Println(counter)</text>
<!-- ====== THE FIX (right of collision) ====== -->
<g filter="url(#shadow)">
<rect x="520" y="110" width="310" height="170" rx="10" fill="none" stroke="#1e293b" stroke-width="1" opacity="0.6"/>
</g>
<!-- shared variable with mutex gate -->
<rect x="640" y="165" width="100" height="36" rx="6" fill="url(#g-shared)" stroke="#f59e0b" stroke-width="1.5" stroke-opacity="0.6"/>
<text x="690" y="188" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="11" fill="#fbbf24">counter</text>
<!-- mutex gate / barrier -->
<rect x="618" y="152" width="8" height="60" rx="3" fill="url(#g-mutex)" stroke="#fbbf24" stroke-width="1"/>
<text x="622" y="146" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="8" fill="#fbbf24">🔒</text>
<text x="622" y="225" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" fill="#fbbf24">Mutex</text>
<!-- G1 passes through (solid = has lock) -->
<circle cx="558" cy="155" r="16" fill="none" stroke="#f472b6" stroke-width="1.5"/>
<text x="558" y="160" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="11" font-weight="700" fill="#f472b6">G1</text>
<line x1="574" y1="162" x2="614" y2="175" stroke="#f472b6" stroke-width="1.5" marker-end="url(#arr-rose)"/>
<text x="585" y="158" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="7" fill="#94a3b8">has lock</text>
<!-- G2 waits (dashed outline = queued) -->
<circle cx="558" cy="210" r="16" fill="none" stroke="#60a5fa" stroke-width="1.5" stroke-dasharray="3,3"/>
<text x="558" y="215" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="11" font-weight="700" fill="#60a5fa" opacity="0.5">G2</text>
<text x="558" y="237" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="7" fill="#64748b">waiting</text>
<!-- label -->
<text x="675" y="132" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="10" fill="#cbd5e1">Mutex serializes access</text>
<!-- sequential timing bars (non-overlapping) -->
<text x="745" y="168" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" fill="#94a3b8">sequential:</text>
<rect x="745" y="174" width="60" height="7" rx="2" fill="#6b2048" opacity="0.8"/>
<text x="749" y="181" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="6" fill="#e2e8f0">G1</text>
<rect x="809" y="174" width="60" height="7" rx="2" fill="#1e3a5f" opacity="0.8"/>
<text x="813" y="181" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="6" fill="#e2e8f0">G2</text>
<!-- no overlap indicator -->
<text x="807" y="195" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="7" fill="#2dd4bf">no overlap</text>
<!-- SAFE checkmark -->
<circle cx="780" cy="215" r="12" fill="none" stroke="#2dd4bf" stroke-width="1.5"/>
<path d="M774,215 L778,220 L787,210" fill="none" stroke="#2dd4bf" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<text x="798" y="219" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="11" font-weight="700" fill="#2dd4bf" letter-spacing="1">SAFE</text>
<!-- ====== BOTTOM SECTION: COMMON PATTERNS ====== -->
<text x="52" y="310" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="10" font-weight="700" fill="#fbbf24" letter-spacing="2.5" opacity="0.9">COMMON PATTERNS</text>
<!-- ====== 1. MAP RACE ====== -->
<g filter="url(#shadow)">
<rect x="52" y="325" width="240" height="270" rx="10" fill="none" stroke="#1e293b" stroke-width="1" opacity="0.6"/>
</g>
<!-- title bar -->
<rect x="52" y="325" width="240" height="28" rx="10" fill="none"/>
<rect x="52" y="343" width="240" height="10" fill="none"/>
<text x="172" y="344" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="11" font-weight="700" fill="#f472b6">1. Map Race</text>
<!-- map icon -->
<rect x="112" y="368" width="120" height="60" rx="6" fill="none" stroke="#f59e0b" stroke-width="1" stroke-opacity="0.3"/>
<text x="122" y="386" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#fbbf24">"a": 1</text>
<text x="122" y="400" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#fbbf24">"b": 2</text>
<text x="122" y="414" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#fbbf24">"c": 3</text>
<text x="172" y="365" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="8" fill="#94a3b8">map[string]int</text>
<!-- G badges pointing at map -->
<circle cx="82" cy="378" r="11" fill="none" stroke="#f472b6" stroke-width="1.2"/>
<text x="82" y="382" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" font-weight="700" fill="#f472b6">G1</text>
<line x1="93" y1="382" x2="110" y2="390" stroke="#f472b6" stroke-width="1.2" marker-end="url(#arr-rose)"/>
<circle cx="82" cy="410" r="11" fill="none" stroke="#60a5fa" stroke-width="1.2"/>
<text x="82" y="414" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" font-weight="700" fill="#60a5fa">G2</text>
<line x1="93" y1="406" x2="110" y2="400" stroke="#60a5fa" stroke-width="1.2" marker-end="url(#arr-blue)"/>
<!-- write/read labels -->
<text x="244" y="385" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" font-weight="600" fill="#f472b6">write</text>
<text x="244" y="415" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" font-weight="600" fill="#60a5fa">read</text>
<!-- lightning bolt -->
<g transform="translate(250,392)" opacity="0.9">
<path d="M0,-8 L-3,0 L1,0 L-2,8 L4,-1 L0,-1 Z" fill="#f472b6" stroke="#ec4899" stroke-width="0.5"/>
</g>
<!-- code -->
<text x="80" y="448" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#e2e8f0">m["a"] = 1</text>
<text x="185" y="448" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#64748b">vs</text>
<text x="200" y="448" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#e2e8f0">_ = m["a"]</text>
<!-- divider -->
<line x1="72" y1="460" x2="272" y2="460" stroke="#1e293b" stroke-width="0.5"/>
<!-- FIX -->
<circle cx="80" cy="478" r="8" fill="none" stroke="#2dd4bf" stroke-width="1"/>
<path d="M76,478 L79,481 L85,475" fill="none" stroke="#2dd4bf" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<text x="93" y="475" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" font-weight="700" fill="#2dd4bf">FIX</text>
<text x="80" y="498" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#e2e8f0">var mu sync.Mutex</text>
<text x="80" y="512" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#e2e8f0">mu.Lock()</text>
<text x="80" y="526" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" fill="#94a3b8">or use</text>
<text x="120" y="526" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#2dd4bf">sync.Map</text>
<!-- ====== 2. LOOP VARIABLE CAPTURE ====== -->
<g filter="url(#shadow)">
<rect x="310" y="325" width="240" height="270" rx="10" fill="none" stroke="#1e293b" stroke-width="1" opacity="0.6"/>
</g>
<!-- title bar -->
<rect x="310" y="325" width="240" height="28" rx="10" fill="none"/>
<rect x="310" y="343" width="240" height="10" fill="none"/>
<text x="430" y="344" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="11" font-weight="700" fill="#fbbf24">2. Loop Variable Capture</text>
<!-- loop counter i -->
<g transform="translate(340,368)">
<!-- i changing -->
<rect x="0" y="0" width="26" height="20" rx="4" fill="none" stroke="#fbbf24" stroke-width="1"/>
<text x="13" y="14" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="10" fill="#fbbf24">i</text>
<text x="34" y="14" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" fill="#94a3b8"></text>
<text x="46" y="14" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#64748b">0→1→2→3→4</text>
</g>
<!-- goroutines all pointing at same i -->
<g transform="translate(340,400)">
<!-- goroutine circles pointing at i -->
<circle cx="15" cy="15" r="10" fill="none" stroke="#f472b6" stroke-width="1"/>
<text x="15" y="18" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="7" font-weight="700" fill="#f472b6">g0</text>
<circle cx="45" cy="15" r="10" fill="none" stroke="#f472b6" stroke-width="1"/>
<text x="45" y="18" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="7" font-weight="700" fill="#f472b6">g1</text>
<circle cx="75" cy="15" r="10" fill="none" stroke="#f472b6" stroke-width="1"/>
<text x="75" y="18" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="7" font-weight="700" fill="#f472b6">g2</text>
<circle cx="105" cy="15" r="10" fill="none" stroke="#f472b6" stroke-width="1"/>
<text x="105" y="18" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="7" font-weight="700" fill="#f472b6">g3</text>
<circle cx="135" cy="15" r="10" fill="none" stroke="#f472b6" stroke-width="1"/>
<text x="135" y="18" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="7" font-weight="700" fill="#f472b6">g4</text>
</g>
<!-- speech bubbles all saying "5" (broken) -->
<text x="340" y="443" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" fill="#f472b6" font-weight="600">all read final value:</text>
<g transform="translate(340,448)">
<rect x="0" y="0" width="22" height="16" rx="3" fill="none" stroke="#f472b6" stroke-width="0.8"/>
<text x="11" y="12" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#f472b6">5</text>
<rect x="28" y="0" width="22" height="16" rx="3" fill="none" stroke="#f472b6" stroke-width="0.8"/>
<text x="39" y="12" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#f472b6">5</text>
<rect x="56" y="0" width="22" height="16" rx="3" fill="none" stroke="#f472b6" stroke-width="0.8"/>
<text x="67" y="12" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#f472b6">5</text>
<rect x="84" y="0" width="22" height="16" rx="3" fill="none" stroke="#f472b6" stroke-width="0.8"/>
<text x="95" y="12" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#f472b6">5</text>
<rect x="112" y="0" width="22" height="16" rx="3" fill="none" stroke="#f472b6" stroke-width="0.8"/>
<text x="123" y="12" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#f472b6">5</text>
</g>
<!-- fixed version -->
<text x="340" y="486" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" fill="#2dd4bf" font-weight="600">fixed — each sees own value:</text>
<g transform="translate(340,491)">
<rect x="0" y="0" width="22" height="16" rx="3" fill="none" stroke="#2dd4bf" stroke-width="0.8"/>
<text x="11" y="12" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#2dd4bf">0</text>
<rect x="28" y="0" width="22" height="16" rx="3" fill="none" stroke="#2dd4bf" stroke-width="0.8"/>
<text x="39" y="12" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#2dd4bf">1</text>
<rect x="56" y="0" width="22" height="16" rx="3" fill="none" stroke="#2dd4bf" stroke-width="0.8"/>
<text x="67" y="12" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#2dd4bf">2</text>
<rect x="84" y="0" width="22" height="16" rx="3" fill="none" stroke="#2dd4bf" stroke-width="0.8"/>
<text x="95" y="12" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#2dd4bf">3</text>
<rect x="112" y="0" width="22" height="16" rx="3" fill="none" stroke="#2dd4bf" stroke-width="0.8"/>
<text x="123" y="12" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#2dd4bf">4</text>
</g>
<!-- divider -->
<line x1="330" y1="518" x2="530" y2="518" stroke="#1e293b" stroke-width="0.5"/>
<!-- FIX -->
<circle cx="338" cy="536" r="8" fill="none" stroke="#2dd4bf" stroke-width="1"/>
<path d="M334,536 L337,539 L343,533" fill="none" stroke="#2dd4bf" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<text x="351" y="533" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" font-weight="700" fill="#2dd4bf">FIX</text>
<text x="338" y="556" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" fill="#cbd5e1">Go 1.22+ fixes this</text>
<text x="338" y="570" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" fill="#94a3b8">or pass as func argument</text>
<!-- ====== 3. CHECK-THEN-ACT ====== -->
<g filter="url(#shadow)">
<rect x="568" y="325" width="250" height="270" rx="10" fill="none" stroke="#1e293b" stroke-width="1" opacity="0.6"/>
</g>
<!-- title bar -->
<rect x="568" y="325" width="250" height="28" rx="10" fill="none"/>
<rect x="568" y="343" width="250" height="10" fill="none"/>
<text x="693" y="344" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="11" font-weight="700" fill="#60a5fa">3. Check-Then-Act</text>
<!-- two sequential steps: check → act -->
<!-- G1 path -->
<text x="590" y="374" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" fill="#f472b6" font-weight="600">G1</text>
<rect x="610" y="362" width="72" height="20" rx="4" fill="none" stroke="#60a5fa" stroke-width="1"/>
<text x="646" y="376" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="8" fill="#e2e8f0">if !exists</text>
<!-- gap (the danger zone) highlighted in rose -->
<rect x="686" y="362" width="26" height="20" rx="2" fill="none" stroke="#f472b6" stroke-width="0.8" stroke-dasharray="2,2"/>
<text x="699" y="376" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="6" fill="#f472b6">gap</text>
<rect x="716" y="362" width="56" height="20" rx="4" fill="none" stroke="#60a5fa" stroke-width="1"/>
<text x="744" y="376" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="8" fill="#e2e8f0">create</text>
<!-- G2 path (same thing, offset) -->
<text x="590" y="404" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" fill="#60a5fa" font-weight="600">G2</text>
<rect x="618" y="392" width="72" height="20" rx="4" fill="none" stroke="#60a5fa" stroke-width="1"/>
<text x="654" y="406" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="8" fill="#e2e8f0">if !exists</text>
<rect x="724" y="392" width="56" height="20" rx="4" fill="none" stroke="#60a5fa" stroke-width="1"/>
<text x="752" y="406" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="8" fill="#e2e8f0">create</text>
<!-- both pass check arrows -->
<text x="610" y="430" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" fill="#94a3b8">both see</text>
<text x="660" y="430" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="8" fill="#fbbf24">false</text>
<text x="694" y="430" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" fill="#94a3b8">→ both create</text>
<!-- double creation / corruption indicator -->
<rect x="600" y="440" width="196" height="32" rx="5" fill="none" stroke="#f472b6" stroke-width="1" stroke-dasharray="3,3"/>
<text x="698" y="456" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" fill="#f472b6" font-weight="600">double creation / corruption</text>
<text x="698" y="468" text-anchor="middle" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="8" fill="#64748b">the check and act are not atomic</text>
<!-- divider -->
<line x1="588" y1="484" x2="798" y2="484" stroke="#1e293b" stroke-width="0.5"/>
<!-- FIX -->
<circle cx="596" cy="502" r="8" fill="none" stroke="#2dd4bf" stroke-width="1"/>
<path d="M592,502 L595,505 L601,499" fill="none" stroke="#2dd4bf" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<text x="609" y="499" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" font-weight="700" fill="#2dd4bf">FIX</text>
<text x="596" y="522" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" fill="#cbd5e1">use atomic CAS or mutex</text>
<text x="596" y="536" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" fill="#cbd5e1">around both operations</text>
<text x="596" y="558" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="8" fill="#e2e8f0">mu.Lock()</text>
<text x="596" y="570" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="8" fill="#64748b">// check + act together</text>
<text x="596" y="582" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="8" fill="#e2e8f0">mu.Unlock()</text>
<!-- ====== RIGHT PANEL ====== -->
<g filter="url(#shadow)">
<rect x="860" y="88" width="310" height="506" rx="12" fill="url(#g-panel)" stroke="#1e293b" stroke-width="1"/>
</g>
<!-- 1. RACE DETECTOR section -->
<text x="884" y="116" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="10" font-weight="700" fill="#f1f5f9" letter-spacing="1.5">RACE DETECTOR</text>
<line x1="884" y1="122" x2="1146" y2="122" stroke="#1e293b" stroke-width="0.5"/>
<!-- commands -->
<text x="896" y="144" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="10" fill="#2dd4bf">go test -race ./...</text>
<text x="896" y="164" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="10" fill="#2dd4bf">go run -race main.go</text>
<text x="896" y="184" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="10" fill="#2dd4bf">go build -race</text>
<!-- description -->
<text x="896" y="206" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" fill="#94a3b8">~5-10x overhead, use in tests + CI,</text>
<text x="896" y="220" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" fill="#94a3b8">not in production.</text>
<!-- divider -->
<line x1="884" y1="234" x2="1146" y2="234" stroke="#1e293b" stroke-width="0.5"/>
<!-- 2. RACE DETECTOR OUTPUT section -->
<text x="884" y="254" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="10" font-weight="700" fill="#f1f5f9" letter-spacing="1.5">DETECTOR OUTPUT</text>
<!-- terminal box -->
<rect x="884" y="264" width="262" height="110" rx="6" fill="none" stroke="#1e293b" stroke-width="1"/>
<!-- terminal dots -->
<circle cx="896" cy="276" r="3" fill="#ec4899" fill-opacity="0.7"/>
<circle cx="906" cy="276" r="3" fill="#fbbf24" fill-opacity="0.7"/>
<circle cx="916" cy="276" r="3" fill="#2dd4bf" fill-opacity="0.7"/>
<!-- terminal content -->
<text x="896" y="298" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#f472b6" font-weight="700">WARNING: DATA RACE</text>
<text x="896" y="314" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="8" fill="#e2e8f0">Read at 0x00c... by goroutine 7:</text>
<text x="896" y="328" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="8" fill="#64748b"> main.go:42 +0x38</text>
<text x="896" y="346" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="8" fill="#e2e8f0">Previous write at 0x00c...</text>
<text x="896" y="360" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="8" fill="#e2e8f0"> by goroutine 6:</text>
<!-- divider -->
<line x1="884" y1="388" x2="1146" y2="388" stroke="#1e293b" stroke-width="0.5"/>
<!-- 3. KEY INSIGHT section -->
<text x="884" y="408" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="10" font-weight="700" fill="#f1f5f9" letter-spacing="1.5">KEY INSIGHT</text>
<!-- quote box -->
<rect x="884" y="418" width="262" height="88" rx="6" fill="none" stroke="#14b8a6" stroke-width="1" stroke-opacity="0.2"/>
<!-- quote bar -->
<rect x="884" y="418" width="3" height="88" rx="1" fill="#1a5c52"/>
<text x="900" y="438" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="10" fill="#cbd5e1" font-style="italic">If two goroutines touch the</text>
<text x="900" y="454" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="10" fill="#cbd5e1" font-style="italic">same variable and at least one</text>
<text x="900" y="470" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="10" fill="#cbd5e1" font-style="italic">writes — that's a race.</text>
<text x="900" y="494" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="10" fill="#2dd4bf" font-weight="600">The fix: mutex, atomic, or channel.</text>
<!-- divider -->
<line x1="884" y1="516" x2="1146" y2="516" stroke="#1e293b" stroke-width="0.5"/>
<!-- quick reference -->
<text x="884" y="536" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="10" font-weight="700" fill="#f1f5f9" letter-spacing="1.5">QUICK REF</text>
<text x="896" y="556" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#fbbf24">sync.Mutex</text>
<text x="1000" y="556" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" fill="#94a3b8">exclusive lock</text>
<text x="896" y="572" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#fbbf24">sync/atomic</text>
<text x="1000" y="572" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" fill="#94a3b8">lock-free ops</text>
<text x="896" y="588" font-family="'JetBrains Mono','SF Mono','Fira Code',monospace" font-size="9" fill="#fbbf24">chan T</text>
<text x="1000" y="588" font-family="'Inter','SF Pro Display',system-ui,sans-serif" font-size="9" fill="#94a3b8">ownership transfer</text>
</svg>

After

Width:  |  Height:  |  Size: 31 KiB

638
testdata/svg/sync-primitives.svg vendored Normal file
View file

@ -0,0 +1,638 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 700">
<defs>
<!-- drop shadow -->
<filter id="shadow" x="-6%" y="-6%" width="112%" height="116%">
<feDropShadow dx="1" dy="2" stdDeviation="5" flood-color="#000" flood-opacity="0.35"/>
</filter>
<!-- title glow -->
<filter id="title-glow" x="-10%" y="-10%" width="120%" height="120%">
<feGaussianBlur in="SourceAlpha" stdDeviation="8" result="blur"/>
<feFlood flood-color="#60a5fa" flood-opacity="0.25" result="color"/>
<feComposite in="color" in2="blur" operator="in" result="glow"/>
<feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<!-- lock glow -->
<filter id="glow-teal" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur"/>
<feFlood flood-color="#2dd4bf" flood-opacity="0.3" result="color"/>
<feComposite in="color" in2="blur" operator="in" result="glow"/>
<feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glow-violet" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur"/>
<feFlood flood-color="#a78bfa" flood-opacity="0.3" result="color"/>
<feComposite in="color" in2="blur" operator="in" result="glow"/>
<feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glow-amber" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur"/>
<feFlood flood-color="#fbbf24" flood-opacity="0.3" result="color"/>
<feComposite in="color" in2="blur" operator="in" result="glow"/>
<feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glow-rose" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur in="SourceAlpha" stdDeviation="3" result="blur"/>
<feFlood flood-color="#f472b6" flood-opacity="0.25" result="color"/>
<feComposite in="color" in2="blur" operator="in" result="glow"/>
<feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<!-- arrow markers -->
<marker id="arr-blue" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#60a5fa"/>
</marker>
<marker id="arr-teal" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#2dd4bf"/>
</marker>
<marker id="arr-amber" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#fbbf24"/>
</marker>
<marker id="arr-violet" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#a78bfa"/>
</marker>
<marker id="arr-rose" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#f472b6"/>
</marker>
<!-- ambient background glows -->
<radialGradient id="glow-tl" cx="220" cy="230" r="300" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#3b82f6" stop-opacity="0.04"/><stop offset="100%" stop-color="#3b82f6" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-tr" cx="620" cy="230" r="300" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#8b5cf6" stop-opacity="0.04"/><stop offset="100%" stop-color="#8b5cf6" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-bl" cx="220" cy="500" r="300" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#14b8a6" stop-opacity="0.04"/><stop offset="100%" stop-color="#14b8a6" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-br" cx="620" cy="500" r="300" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#f59e0b" stop-opacity="0.03"/><stop offset="100%" stop-color="#f59e0b" stop-opacity="0"/>
</radialGradient>
<!-- right panel gradient -->
<linearGradient id="g-panel" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#141d2e" stop-opacity="0.9"/>
<stop offset="100%" stop-color="#0b1120" stop-opacity="0.95"/>
</linearGradient>
<!-- quote accent stripe -->
<linearGradient id="g-accent" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#3b82f6"/>
<stop offset="25%" stop-color="#14b8a6"/>
<stop offset="50%" stop-color="#f59e0b"/>
<stop offset="75%" stop-color="#8b5cf6"/>
<stop offset="100%" stop-color="#ec4899"/>
</linearGradient>
<!-- critical section glow -->
<linearGradient id="g-critical" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#3b82f6" stop-opacity="0.12"/>
<stop offset="100%" stop-color="#3b82f6" stop-opacity="0.03"/>
</linearGradient>
<!-- pool interior gradient -->
<radialGradient id="g-pool" cx="0.5" cy="0.5" r="0.5">
<stop offset="0%" stop-color="#14b8a6" stop-opacity="0.1"/>
<stop offset="100%" stop-color="#14b8a6" stop-opacity="0.02"/>
</radialGradient>
<!-- counter display gradient -->
<linearGradient id="g-counter" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#2a1800"/><stop offset="100%" stop-color="#1a0f00"/>
</linearGradient>
<!-- goroutine ball gradients -->
<radialGradient id="gb-blue" cx="0.4" cy="0.35" r="0.6">
<stop offset="0%" stop-color="#93c5fd"/><stop offset="100%" stop-color="#2563eb"/>
</radialGradient>
<radialGradient id="gb-teal" cx="0.4" cy="0.35" r="0.6">
<stop offset="0%" stop-color="#5eead4"/><stop offset="100%" stop-color="#0d9488"/>
</radialGradient>
<radialGradient id="gb-amber" cx="0.4" cy="0.35" r="0.6">
<stop offset="0%" stop-color="#fde68a"/><stop offset="100%" stop-color="#d97706"/>
</radialGradient>
<radialGradient id="gb-violet" cx="0.4" cy="0.35" r="0.6">
<stop offset="0%" stop-color="#c4b5fd"/><stop offset="100%" stop-color="#7c3aed"/>
</radialGradient>
<radialGradient id="gb-rose" cx="0.4" cy="0.35" r="0.6">
<stop offset="0%" stop-color="#fda4af"/><stop offset="100%" stop-color="#e11d48"/>
</radialGradient>
</defs>
<style>
.bg { fill: #0f172a; }
.title { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 26px; font-weight: 700; fill: #f1f5f9; }
.subtitle { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 12px; font-weight: 400; fill: #94a3b8; }
.mech-title { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 14px; font-weight: 700; fill: #f1f5f9; }
.mech-sub { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 400; fill: #94a3b8; }
.desc { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10.5px; fill: #cbd5e1; }
.code { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 10px; fill: #e2e8f0; }
.code-sm { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 9px; fill: #e2e8f0; }
.code-xs { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 8px; fill: #e2e8f0; }
.g-label { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 9px; font-weight: 700; }
.note { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10px; fill: #64748b; }
.label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10px; fill: #94a3b8; }
.label-sm { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 9px; fill: #94a3b8; }
.panel-title { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 12px; font-weight: 700; fill: #f1f5f9; letter-spacing: 0.5px; }
.panel-item { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10.5px; fill: #94a3b8; }
.panel-code { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 10px; fill: #e2e8f0; }
.quote { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 600; fill: #f1f5f9; }
.quote-note { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10px; fill: #64748b; }
.lane-label { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 10px; font-weight: 700; }
</style>
<!-- background -->
<g opacity="0.05">
<pattern id="dots" width="40" height="40" patternUnits="userSpaceOnUse">
<circle cx="20" cy="20" r="0.8" fill="#94a3b8"/>
</pattern>
<rect width="1200" height="700" fill="url(#dots)"/>
</g>
<!-- ambient glows -->
<rect width="1200" height="700" fill="url(#glow-tl)"/>
<rect width="1200" height="700" fill="url(#glow-tr)"/>
<rect width="1200" height="700" fill="url(#glow-bl)"/>
<rect width="1200" height="700" fill="url(#glow-br)"/>
<!-- ================================================================ -->
<!-- TITLE -->
<!-- ================================================================ -->
<g filter="url(#title-glow)">
<text class="title" x="420" y="38" text-anchor="middle" dominant-baseline="central" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="26px" font-weight="700" fill="#f1f5f9">Go sync — Locks, Gates &amp; Pools</text>
</g>
<text class="subtitle" x="420" y="62" text-anchor="middle" dominant-baseline="central" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="12px" font-weight="400" fill="#94a3b8">mutex · once · pool · atomic · rwmutex</text>
<!-- ================================================================ -->
<!-- TOP-LEFT: MUTEX (x=52, y=110, ~370x250) -->
<!-- ================================================================ -->
<g transform="translate(52, 110)">
<text class="mech-title" x="0" y="0" fill="#60a5fa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="14px" font-weight="700">Mutex</text>
<text class="mech-sub" x="54" y="0" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#94a3b8">— exclusive gate</text>
<!-- the gate barrier -->
<g filter="url(#shadow)">
<!-- gate frame -->
<rect x="148" y="18" width="8" height="70" rx="2" fill="#1e3a5f" stroke="#3b82f6" stroke-width="1.5"/>
<!-- lock icon on gate -->
<g transform="translate(140, 52)">
<!-- lock body -->
<rect x="3" y="6" width="18" height="14" rx="2" fill="#1e3a5f" stroke="#60a5fa" stroke-width="1.2"/>
<!-- lock shackle -->
<path d="M7,6 V3 A5,5 0 0,1 17,3 V6" fill="none" stroke="#60a5fa" stroke-width="1.2"/>
<!-- keyhole -->
<circle cx="12" cy="13" r="2" fill="#60a5fa"/>
<rect x="11" y="14" width="2" height="3" fill="#60a5fa"/>
</g>
</g>
<!-- Lock() label above gate -->
<text class="code-sm" x="152" y="14" text-anchor="middle" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">Lock()</text>
<!-- Unlock() label below gate -->
<text class="code-sm" x="152" y="100" text-anchor="middle" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">Unlock()</text>
<!-- waiting goroutines (left side queue) -->
<g filter="url(#shadow)">
<circle cx="30" cy="38" r="12" fill="#1e3a5f" stroke="#3b82f6" stroke-width="1.2"/>
<text class="g-label" x="30" y="38" text-anchor="middle" dominant-baseline="central" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">G3</text>
</g>
<g filter="url(#shadow)">
<circle cx="62" cy="38" r="12" fill="#1e3a5f" stroke="#3b82f6" stroke-width="1.2"/>
<text class="g-label" x="62" y="38" text-anchor="middle" dominant-baseline="central" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">G2</text>
</g>
<g filter="url(#shadow)">
<circle cx="94" cy="38" r="12" fill="#1e3a5f" stroke="#3b82f6" stroke-width="1.2"/>
<text class="g-label" x="94" y="38" text-anchor="middle" dominant-baseline="central" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">G1</text>
</g>
<!-- waiting arrows pointing at gate -->
<path d="M108,38 L140,38" stroke="#60a5fa" stroke-width="1" fill="none" marker-end="url(#arr-blue)" stroke-dasharray="3,2" opacity="0.5"/>
<!-- label: "waiting" -->
<text class="label-sm" x="62" y="60" text-anchor="middle" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" fill="#94a3b8">waiting</text>
<!-- critical section (right side) -->
<g filter="url(#shadow)">
<rect x="170" y="18" width="190" height="70" rx="8" fill="url(#g-critical)" stroke="#3b82f6" stroke-width="1" stroke-dasharray="4,3" opacity="0.8"/>
</g>
<text class="label-sm" x="265" y="32" text-anchor="middle" fill="#60a5fa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px">critical section</text>
<!-- goroutine inside critical section -->
<g filter="url(#shadow)">
<circle cx="230" cy="60" r="12" fill="#1e3a5f" stroke="#60a5fa" stroke-width="1.5"/>
<text class="g-label" x="230" y="60" text-anchor="middle" dominant-baseline="central" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">G0</text>
</g>
<!-- shared data icon next to goroutine -->
<g transform="translate(260, 48)">
<rect width="40" height="24" rx="3" fill="#0f172a" stroke="#3b82f6" stroke-width="0.8" opacity="0.7"/>
<text class="code-xs" x="20" y="10" text-anchor="middle" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="8px">data</text>
<text class="code-xs" x="20" y="20" text-anchor="middle" fill="#475569" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="8px">mutex</text>
</g>
<!-- ── RWMutex sub-section ──────────────────────────── -->
<text class="mech-sub" x="0" y="126" fill="#60a5fa" font-weight="600" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px">RWMutex — two lanes</text>
<!-- R lane (wide, multiple readers) -->
<g transform="translate(0, 138)">
<!-- R lane track -->
<rect x="0" y="0" width="360" height="28" rx="6" fill="#3b82f6" fill-opacity="0.06" stroke="#3b82f6" stroke-width="0.8" stroke-opacity="0.3"/>
<text class="lane-label" x="6" y="18" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" font-weight="700">R</text>
<!-- multiple readers flowing through -->
<g filter="url(#shadow)">
<circle cx="50" cy="14" r="9" fill="#0f3d2e" stroke="#14b8a6" stroke-width="1"/>
<text class="g-label" x="50" y="14" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" style="font-size:7px" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">R1</text>
</g>
<g filter="url(#shadow)">
<circle cx="80" cy="14" r="9" fill="#0f3d2e" stroke="#14b8a6" stroke-width="1"/>
<text class="g-label" x="80" y="14" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" style="font-size:7px" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">R2</text>
</g>
<g filter="url(#shadow)">
<circle cx="110" cy="14" r="9" fill="#0f3d2e" stroke="#14b8a6" stroke-width="1"/>
<text class="g-label" x="110" y="14" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" style="font-size:7px" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">R3</text>
</g>
<!-- flow arrows in R lane -->
<path d="M124,14 L148,14" stroke="#2dd4bf" stroke-width="0.8" fill="none" marker-end="url(#arr-teal)" opacity="0.4"/>
<path d="M160,14 L184,14" stroke="#2dd4bf" stroke-width="0.8" fill="none" marker-end="url(#arr-teal)" opacity="0.3"/>
<path d="M196,14 L220,14" stroke="#2dd4bf" stroke-width="0.8" fill="none" marker-end="url(#arr-teal)" opacity="0.2"/>
<!-- readers exiting -->
<g filter="url(#shadow)" opacity="0.5">
<circle cx="260" cy="14" r="9" fill="#0f3d2e" stroke="#14b8a6" stroke-width="1"/>
<text class="g-label" x="260" y="14" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" style="font-size:7px" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">R4</text>
</g>
<g filter="url(#shadow)" opacity="0.4">
<circle cx="290" cy="14" r="9" fill="#0f3d2e" stroke="#14b8a6" stroke-width="1"/>
<text class="g-label" x="290" y="14" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" style="font-size:7px" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">R5</text>
</g>
<text class="label-sm" x="340" y="18" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px">concurrent</text>
</g>
<!-- W lane (narrow, exclusive) -->
<g transform="translate(0, 172)">
<!-- W lane track -->
<rect x="0" y="0" width="360" height="28" rx="6" fill="#f59e0b" fill-opacity="0.06" stroke="#f59e0b" stroke-width="0.8" stroke-opacity="0.3"/>
<text class="lane-label" x="6" y="18" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" font-weight="700">W</text>
<!-- writer blocked, waiting -->
<g filter="url(#shadow)">
<circle cx="50" cy="14" r="9" fill="#3a1a03" stroke="#f59e0b" stroke-width="1"/>
<text class="g-label" x="50" y="14" text-anchor="middle" dominant-baseline="central" fill="#fbbf24" style="font-size:7px" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">W1</text>
</g>
<!-- blocked indicator -->
<line x1="68" y1="14" x2="90" y2="14" stroke="#f59e0b" stroke-width="1" stroke-dasharray="2,2" opacity="0.4"/>
<!-- gate bar blocking the writer -->
<rect x="95" y="2" width="4" height="24" rx="1" fill="#f59e0b" opacity="0.5"/>
<text class="label-sm" x="115" y="18" fill="#fbbf24" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px">exclusive — waits for readers</text>
</g>
<text class="code-sm" x="0" y="216" fill="#475569" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">mu.RLock() // many readers</text>
<text class="code-sm" x="0" y="228" fill="#475569" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">mu.Lock() // one writer</text>
</g>
<!-- ================================================================ -->
<!-- TOP-RIGHT: sync.Once (x=440, y=110, ~370x250) -->
<!-- ================================================================ -->
<g transform="translate(440, 110)">
<text class="mech-title" x="0" y="0" fill="#a78bfa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="14px" font-weight="700">sync.Once</text>
<text class="mech-sub" x="76" y="0" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#94a3b8">— one-time switch</text>
<!-- the lever/switch mechanism -->
<g filter="url(#shadow)" transform="translate(130, 20)">
<!-- switch base plate -->
<rect x="0" y="30" width="80" height="8" rx="4" fill="#1e1845" stroke="#8b5cf6" stroke-width="1.2"/>
<!-- switch track -->
<rect x="35" y="2" width="10" height="32" rx="3" fill="#1e1845" stroke="#8b5cf6" stroke-width="1"/>
<!-- lever (pulled position — tilted right) -->
<line x1="40" y1="8" x2="68" y2="28" stroke="#a78bfa" stroke-width="3" stroke-linecap="round"/>
<!-- lever knob -->
<circle cx="68" cy="28" r="5" fill="#a78bfa"/>
<!-- "FIRED" indicator -->
<circle cx="72" cy="10" r="4" fill="#8b5cf6" opacity="0.8"/>
<circle cx="72" cy="10" r="6" fill="none" stroke="#a78bfa" stroke-width="0.5" opacity="0.4"/>
</g>
<!-- once.Do(func) label -->
<text class="code-sm" x="170" y="78" text-anchor="middle" fill="#a78bfa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">once.Do(fn)</text>
<!-- goroutines reaching for the switch -->
<!-- G1 — the one that triggered it (connected with solid line, teal glow) -->
<g filter="url(#glow-violet)">
<circle cx="52" cy="42" r="12" fill="#1e1845" stroke="#8b5cf6" stroke-width="1.5"/>
<text class="g-label" x="52" y="42" text-anchor="middle" dominant-baseline="central" fill="#a78bfa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">G1</text>
</g>
<path d="M66,42 L128,42" stroke="#a78bfa" stroke-width="1.2" fill="none" marker-end="url(#arr-violet)"/>
<text class="label-sm" x="97" y="36" fill="#a78bfa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px">triggers</text>
<!-- G2, G3 — latecomers, get cached result -->
<g filter="url(#shadow)">
<circle cx="30" cy="76" r="12" fill="#1e1845" stroke="#8b5cf6" stroke-width="1" opacity="0.6"/>
<text class="g-label" x="30" y="76" text-anchor="middle" dominant-baseline="central" fill="#a78bfa" opacity="0.6" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">G2</text>
</g>
<g filter="url(#shadow)">
<circle cx="72" cy="76" r="12" fill="#1e1845" stroke="#8b5cf6" stroke-width="1" opacity="0.6"/>
<text class="g-label" x="72" y="76" text-anchor="middle" dominant-baseline="central" fill="#a78bfa" opacity="0.6" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">G3</text>
</g>
<!-- dashed lines from latecomers to result cache -->
<path d="M44,76 L248,76" stroke="#a78bfa" stroke-width="0.8" fill="none" stroke-dasharray="3,2" opacity="0.4"/>
<path d="M86,76 L248,76" stroke="#a78bfa" stroke-width="0.8" fill="none" stroke-dasharray="3,2" opacity="0.4"/>
<!-- result cache box -->
<g filter="url(#shadow)">
<rect x="252" y="28" width="100" height="58" rx="6" fill="#1e1845" stroke="#8b5cf6" stroke-width="1" stroke-dasharray="4,3"/>
<text class="label-sm" x="302" y="44" text-anchor="middle" fill="#a78bfa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px">cached result</text>
<text class="code-xs" x="302" y="58" text-anchor="middle" fill="#e2e8f0" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="8px">initialized</text>
<text class="code-xs" x="302" y="70" text-anchor="middle" fill="#64748b" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="8px">run only once</text>
</g>
<!-- action arrow from switch to result -->
<path d="M214,46 L248,46" stroke="#a78bfa" stroke-width="1" fill="none" marker-end="url(#arr-violet)"/>
<text class="label-sm" x="231" y="40" fill="#a78bfa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px">init</text>
<!-- ── sync.OnceValue sub-section ──────────────────── -->
<text class="mech-sub" x="0" y="116" fill="#a78bfa" font-weight="600" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px">OnceValue — returns a value</text>
<g transform="translate(0, 128)">
<!-- simplified switch (smaller) -->
<g filter="url(#shadow)">
<rect x="0" y="4" width="50" height="24" rx="12" fill="#1e1845" stroke="#8b5cf6" stroke-width="1"/>
<!-- toggle dot (in "on" position) -->
<circle cx="37" cy="16" r="8" fill="#8b5cf6" opacity="0.7"/>
<text class="code-xs" x="37" y="16" text-anchor="middle" dominant-baseline="central" fill="#f1f5f9" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="8px">ON</text>
</g>
<path d="M54,16 L80,16" stroke="#a78bfa" stroke-width="0.8" fill="none" marker-end="url(#arr-violet)" opacity="0.6"/>
<!-- return value -->
<g filter="url(#shadow)">
<rect x="84" y="4" width="80" height="24" rx="4" fill="#1e1845" stroke="#8b5cf6" stroke-width="0.8"/>
<text class="code-xs" x="124" y="16" text-anchor="middle" dominant-baseline="central" fill="#a78bfa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="8px">→ T</text>
</g>
<text class="code-sm" x="180" y="16" dominant-baseline="central" fill="#475569" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">f := sync.OnceValue(init)</text>
</g>
<g transform="translate(0, 162)">
<text class="code-sm" x="0" y="12" fill="#475569" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">var once sync.Once</text>
<text class="code-sm" x="0" y="24" fill="#475569" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">once.Do(func() { /* runs 1x */ })</text>
</g>
<!-- visual: multiple calls, one execution -->
<g transform="translate(0, 196)">
<text class="desc" x="0" y="0" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10.5px" fill="#cbd5e1">All goroutines block until the first Do() completes.</text>
<text class="desc" x="0" y="14" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10.5px" fill="#cbd5e1">Subsequent calls return immediately — zero cost.</text>
</g>
</g>
<!-- ================================================================ -->
<!-- BOTTOM-LEFT: sync.Pool (x=52, y=380, ~370x250) -->
<!-- ================================================================ -->
<g transform="translate(52, 380)">
<text class="mech-title" x="0" y="0" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="14px" font-weight="700">sync.Pool</text>
<text class="mech-sub" x="72" y="0" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#94a3b8">— object recycling</text>
<!-- the pool basin (oval shape) -->
<g filter="url(#shadow)">
<ellipse cx="160" cy="100" rx="130" ry="55" fill="url(#g-pool)" stroke="#14b8a6" stroke-width="1.5"/>
<!-- water-level hint lines -->
<ellipse cx="160" cy="90" rx="110" ry="35" fill="none" stroke="#14b8a6" stroke-width="0.4" opacity="0.2"/>
<ellipse cx="160" cy="98" rx="100" ry="28" fill="none" stroke="#14b8a6" stroke-width="0.3" opacity="0.15"/>
</g>
<!-- objects floating in the pool -->
<g filter="url(#shadow)">
<rect x="100" y="82" width="36" height="16" rx="3" fill="#134e4a" stroke="#14b8a6" stroke-width="0.8"/>
<text class="code-xs" x="118" y="92" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="8px">buf</text>
</g>
<g filter="url(#shadow)">
<rect x="146" y="90" width="36" height="16" rx="3" fill="#134e4a" stroke="#14b8a6" stroke-width="0.8"/>
<text class="code-xs" x="164" y="100" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="8px">conn</text>
</g>
<g filter="url(#shadow)">
<rect x="192" y="84" width="36" height="16" rx="3" fill="#134e4a" stroke="#14b8a6" stroke-width="0.8"/>
<text class="code-xs" x="210" y="94" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="8px">enc</text>
</g>
<g filter="url(#shadow)" opacity="0.5">
<rect x="146" y="110" width="36" height="16" rx="3" fill="#134e4a" stroke="#14b8a6" stroke-width="0.8"/>
<text class="code-xs" x="164" y="120" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="8px">buf</text>
</g>
<!-- Get() arrow — pulling object out (upward) -->
<g>
<path d="M120,78 L120,26" stroke="#2dd4bf" stroke-width="1.5" fill="none" marker-end="url(#arr-teal)"/>
<text class="code-sm" x="108" y="20" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">Get()</text>
</g>
<!-- goroutine receiving from Get -->
<g filter="url(#shadow)">
<circle cx="80" cy="20" r="11" fill="#0f3d2e" stroke="#14b8a6" stroke-width="1.2"/>
<text class="g-label" x="80" y="20" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">G1</text>
</g>
<!-- Put() arrow — returning object (downward) -->
<g>
<path d="M210,26 L210,78" stroke="#2dd4bf" stroke-width="1.5" fill="none" marker-end="url(#arr-teal)"/>
<text class="code-sm" x="218" y="20" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">Put()</text>
</g>
<!-- goroutine returning to Put -->
<g filter="url(#shadow)">
<circle cx="248" cy="20" r="11" fill="#0f3d2e" stroke="#14b8a6" stroke-width="1.2"/>
<text class="g-label" x="248" y="20" text-anchor="middle" dominant-baseline="central" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">G2</text>
</g>
<!-- New() factory on the right side -->
<g transform="translate(300, 70)">
<g filter="url(#shadow)">
<rect x="0" y="0" width="60" height="30" rx="4" fill="#0f3d2e" stroke="#14b8a6" stroke-width="1" stroke-dasharray="3,2"/>
<text class="code-xs" x="30" y="12" text-anchor="middle" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="8px">New()</text>
<text class="code-xs" x="30" y="23" text-anchor="middle" fill="#64748b" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="8px">factory</text>
</g>
<!-- arrow from factory into pool -->
<path d="M0,15 L-8,15" stroke="#2dd4bf" stroke-width="1" fill="none" marker-end="url(#arr-teal)" opacity="0.5"/>
</g>
<!-- GC sweep (dashed rose line) -->
<g>
<path d="M38,130 L282,130" stroke="#f472b6" stroke-width="1" fill="none" stroke-dasharray="4,3" opacity="0.5"/>
<text class="label-sm" x="160" y="144" text-anchor="middle" fill="#f472b6" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px">GC may drain pool</text>
<!-- sweep icon (broom-like) -->
<path d="M28,126 L28,134 M24,130 L32,130" stroke="#f472b6" stroke-width="1" opacity="0.5"/>
</g>
<text class="desc" x="0" y="174" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10.5px" fill="#cbd5e1">Reuse expensive allocations. Not a cache — objects</text>
<text class="desc" x="0" y="186" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10.5px" fill="#cbd5e1">may vanish on any GC cycle. Reset before Put().</text>
</g>
<!-- ================================================================ -->
<!-- BOTTOM-RIGHT: Atomic Operations (x=440, y=380, ~370x250) -->
<!-- ================================================================ -->
<g transform="translate(440, 380)">
<text class="mech-title" x="0" y="0" fill="#fbbf24" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="14px" font-weight="700">Atomic</text>
<text class="mech-sub" x="56" y="0" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="400" fill="#94a3b8">— hardware counter</text>
<!-- digital counter display -->
<g filter="url(#shadow)" transform="translate(100, 18)">
<!-- display housing -->
<rect x="0" y="0" width="140" height="56" rx="6" fill="url(#g-counter)" stroke="#f59e0b" stroke-width="1.5"/>
<!-- inner display bezel -->
<rect x="8" y="8" width="124" height="40" rx="3" fill="#0a0500" stroke="#f59e0b" stroke-width="0.5" opacity="0.6"/>
<!-- LED digits -->
<text x="70" y="28" text-anchor="middle" dominant-baseline="central" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', monospace" font-size="24" font-weight="700">1042</text>
<!-- display scan lines -->
<line x1="10" y1="18" x2="130" y2="18" stroke="#f59e0b" stroke-width="0.3" opacity="0.1"/>
<line x1="10" y1="26" x2="130" y2="26" stroke="#f59e0b" stroke-width="0.3" opacity="0.1"/>
<line x1="10" y1="34" x2="130" y2="34" stroke="#f59e0b" stroke-width="0.3" opacity="0.1"/>
<line x1="10" y1="42" x2="130" y2="42" stroke="#f59e0b" stroke-width="0.3" opacity="0.1"/>
</g>
<!-- "lock-free" badge -->
<g transform="translate(250, 24)">
<rect x="0" y="0" width="70" height="18" rx="9" fill="#3a1a03" stroke="#f59e0b" stroke-width="0.8"/>
<text class="code-xs" x="35" y="12" text-anchor="middle" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="8px">lock-free</text>
</g>
<!-- goroutines simultaneously incrementing -->
<!-- G1 top-left -->
<g filter="url(#shadow)">
<circle cx="46" cy="30" r="11" fill="#3a1a03" stroke="#f59e0b" stroke-width="1.2"/>
<text class="g-label" x="46" y="30" text-anchor="middle" dominant-baseline="central" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">G1</text>
</g>
<path d="M59,34 L96,40" stroke="#fbbf24" stroke-width="1" fill="none" marker-end="url(#arr-amber)" opacity="0.6"/>
<!-- G2 left -->
<g filter="url(#shadow)">
<circle cx="34" cy="60" r="11" fill="#3a1a03" stroke="#f59e0b" stroke-width="1.2"/>
<text class="g-label" x="34" y="60" text-anchor="middle" dominant-baseline="central" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">G2</text>
</g>
<path d="M47,56 L96,48" stroke="#fbbf24" stroke-width="1" fill="none" marker-end="url(#arr-amber)" opacity="0.6"/>
<!-- G3 bottom-left -->
<g filter="url(#shadow)">
<circle cx="56" cy="86" r="11" fill="#3a1a03" stroke="#f59e0b" stroke-width="1.2"/>
<text class="g-label" x="56" y="86" text-anchor="middle" dominant-baseline="central" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px" font-weight="700">G3</text>
</g>
<path d="M69,82 L96,66" stroke="#fbbf24" stroke-width="1" fill="none" marker-end="url(#arr-amber)" opacity="0.6"/>
<!-- operations list -->
<g transform="translate(0, 110)">
<text class="code-sm" x="0" y="0" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">Add()</text>
<text class="label-sm" x="48" y="0" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px">increment/decrement</text>
<text class="code-sm" x="0" y="16" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">Load()</text>
<text class="label-sm" x="48" y="16" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px">read current value</text>
<text class="code-sm" x="0" y="32" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">Store()</text>
<text class="label-sm" x="48" y="32" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px">write new value</text>
<text class="code-sm" x="0" y="48" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">CompareAndSwap()</text>
<text class="label-sm" x="130" y="48" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px">conditional update</text>
</g>
<!-- crossed-out mutex comparison -->
<g transform="translate(240, 108)">
<g filter="url(#shadow)" opacity="0.4">
<rect x="0" y="0" width="100" height="32" rx="4" fill="#1e3a5f" stroke="#3b82f6" stroke-width="0.8"/>
<text class="code-xs" x="50" y="12" text-anchor="middle" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="8px">mu.Lock()</text>
<text class="code-xs" x="50" y="24" text-anchor="middle" fill="#60a5fa" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="8px">counter++</text>
</g>
<!-- X cross-out -->
<line x1="5" y1="5" x2="95" y2="27" stroke="#f472b6" stroke-width="2" opacity="0.7"/>
<line x1="95" y1="5" x2="5" y2="27" stroke="#f472b6" stroke-width="2" opacity="0.7"/>
</g>
<g transform="translate(240, 148)">
<text class="code-xs" x="50" y="0" text-anchor="middle" fill="#64748b" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="8px">use atomic instead</text>
</g>
<text class="desc" x="0" y="178" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10.5px" fill="#cbd5e1">CPU-level instructions. No goroutine blocking.</text>
<text class="desc" x="0" y="190" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10.5px" fill="#cbd5e1">Best for simple counters, flags, and load/store.</text>
</g>
<!-- ================================================================ -->
<!-- RIGHT PANEL (x=860, y=88, w=310) -->
<!-- ================================================================ -->
<g transform="translate(860, 88)">
<!-- panel background -->
<rect x="0" y="0" width="310" height="568" rx="10" fill="url(#g-panel)" stroke="#1e293b" stroke-width="1"/>
<!-- ── When to Use What ─────────────────────────────── -->
<text class="panel-title" x="20" y="28" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="12px" font-weight="700" fill="#f1f5f9" letter-spacing="0.5px">When to Use What</text>
<!-- decision items -->
<g transform="translate(20, 48)">
<!-- atomic -->
<circle cx="6" cy="6" r="4" fill="#f59e0b"/>
<text class="panel-item" x="18" y="10" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10.5px" fill="#94a3b8">Simple counter</text>
<text class="panel-code" x="148" y="10" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px">→ atomic</text>
<!-- mutex -->
<circle cx="6" cy="30" r="4" fill="#3b82f6"/>
<text class="panel-item" x="18" y="34" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10.5px" fill="#94a3b8">Shared struct</text>
<text class="panel-code" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" x="148" y="34" fill="#60a5fa">→ Mutex</text>
<!-- rwmutex -->
<circle cx="6" cy="54" r="4" fill="#3b82f6"/>
<text class="panel-item" x="18" y="58" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10.5px" fill="#94a3b8">Read-heavy</text>
<text class="panel-code" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" x="148" y="58" fill="#60a5fa">→ RWMutex</text>
<!-- once -->
<circle cx="6" cy="78" r="4" fill="#8b5cf6"/>
<text class="panel-item" x="18" y="82" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10.5px" fill="#94a3b8">Init once</text>
<text class="panel-code" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" x="148" y="82" fill="#a78bfa">→ sync.Once</text>
<!-- pool -->
<circle cx="6" cy="102" r="4" fill="#14b8a6"/>
<text class="panel-item" x="18" y="106" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10.5px" fill="#94a3b8">Reuse objects</text>
<text class="panel-code" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" x="148" y="106" fill="#2dd4bf">→ sync.Pool</text>
<!-- channels -->
<circle cx="6" cy="126" r="4" fill="#ec4899"/>
<text class="panel-item" x="18" y="130" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10.5px" fill="#94a3b8">Goroutine coord.</text>
<text class="panel-code" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" x="148" y="130" fill="#f472b6">→ channels</text>
</g>
<!-- ── Cheat Sheet ──────────────────────────────────── -->
<line x1="20" y1="198" x2="290" y2="198" stroke="#1e293b" stroke-width="1"/>
<text class="panel-title" x="20" y="220" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="12px" font-weight="700" fill="#f1f5f9" letter-spacing="0.5px">Cheat Sheet</text>
<g transform="translate(20, 238)">
<text class="panel-code" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" x="0" y="0" fill="#60a5fa">var mu sync.Mutex</text>
<text class="panel-code" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" x="0" y="16" fill="#475569">mu.Lock()</text>
<text class="panel-code" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" x="0" y="32" fill="#475569">defer mu.Unlock()</text>
<text class="panel-code" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" x="0" y="56" fill="#a78bfa">var once sync.Once</text>
<text class="panel-code" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" x="0" y="72" fill="#475569">once.Do(initFunc)</text>
<text class="panel-code" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" x="0" y="96" fill="#2dd4bf">var pool sync.Pool</text>
<text class="panel-code" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" x="0" y="112" fill="#475569">pool.New = func() any {</text>
<text class="panel-code" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" x="0" y="128" fill="#475569"> return &amp;Buffer{}</text>
<text class="panel-code" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" x="0" y="144" fill="#475569">}</text>
<text class="panel-code" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" x="0" y="168" fill="#fbbf24">var count atomic.Int64</text>
<text class="panel-code" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" x="0" y="184" fill="#475569">count.Add(1)</text>
<text class="panel-code" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" x="0" y="200" fill="#475569">v := count.Load()</text>
</g>
<!-- ── Key Insight ──────────────────────────────────── -->
<line x1="20" y1="456" x2="290" y2="456" stroke="#1e293b" stroke-width="1"/>
<text class="panel-title" x="20" y="478" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="12px" font-weight="700" fill="#f1f5f9" letter-spacing="0.5px">Key Insight</text>
<!-- quote box with accent stripe -->
<g transform="translate(20, 490)">
<!-- accent stripe -->
<rect x="0" y="0" width="3" height="54" rx="1.5" fill="url(#g-accent)"/>
<text class="quote" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" fill="#f1f5f9" x="14" y="12">Mutexes protect data.</text>
<text class="quote" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" fill="#f1f5f9" x="14" y="28">Channels coordinate flow.</text>
<text class="quote-note" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#64748b" x="14" y="46">Pick based on whether you're guarding</text>
<text class="quote-note" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#64748b" x="14" y="58">state or passing messages.</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 44 KiB

417
testdata/svg/timers.svg vendored Normal file
View file

@ -0,0 +1,417 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 620">
<defs>
<!-- filters -->
<filter id="shadow" x="-6%" y="-6%" width="112%" height="116%">
<feDropShadow dx="1" dy="2" stdDeviation="5" flood-color="#000" flood-opacity="0.35"/>
</filter>
<filter id="title-glow" x="-10%" y="-10%" width="120%" height="120%">
<feGaussianBlur in="SourceAlpha" stdDeviation="8" result="blur"/>
<feFlood flood-color="#fbbf24" flood-opacity="0.25" result="color"/>
<feComposite in="color" in2="blur" operator="in" result="glow"/>
<feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glow-rose" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur in="SourceAlpha" stdDeviation="3" result="blur"/>
<feFlood flood-color="#ec4899" flood-opacity="0.4" result="color"/>
<feComposite in="color" in2="blur" operator="in" result="glow"/>
<feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<!-- arrow markers -->
<marker id="arr-teal" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#2dd4bf"/>
</marker>
<marker id="arr-amber" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#fbbf24"/>
</marker>
<marker id="arr-blue" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#60a5fa"/>
</marker>
<marker id="arr-rose" viewBox="0 0 12 8" refX="11" refY="4" markerWidth="12" markerHeight="8" orient="auto-start-reverse">
<path d="M0,0 L12,4 L0,8 Z" fill="#f472b6"/>
</marker>
<!-- gradients -->
<radialGradient id="g-ball-amber" cx="0.4" cy="0.35" r="0.6">
<stop offset="0%" stop-color="#fde68a"/><stop offset="100%" stop-color="#d97706"/>
</radialGradient>
<radialGradient id="g-ball-teal" cx="0.4" cy="0.35" r="0.6">
<stop offset="0%" stop-color="#5eead4"/><stop offset="100%" stop-color="#0d9488"/>
</radialGradient>
<radialGradient id="g-ball-blue" cx="0.4" cy="0.35" r="0.6">
<stop offset="0%" stop-color="#93c5fd"/><stop offset="100%" stop-color="#2563eb"/>
</radialGradient>
<!-- ambient glows -->
<radialGradient id="glow-top" cx="300" cy="180" r="340" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#f59e0b" stop-opacity="0.04"/><stop offset="100%" stop-color="#f59e0b" stop-opacity="0"/>
</radialGradient>
<radialGradient id="glow-bot" cx="300" cy="440" r="340" gradientUnits="userSpaceOnUse">
<stop offset="0%" stop-color="#3b82f6" stop-opacity="0.04"/><stop offset="100%" stop-color="#3b82f6" stop-opacity="0"/>
</radialGradient>
<!-- right panel -->
<linearGradient id="g-panel" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#141d2e" stop-opacity="0.9"/><stop offset="100%" stop-color="#0b1120" stop-opacity="0.95"/>
</linearGradient>
<!-- accent stripe -->
<linearGradient id="g-accent" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#f59e0b"/><stop offset="33%" stop-color="#3b82f6"/>
<stop offset="66%" stop-color="#14b8a6"/><stop offset="100%" stop-color="#ec4899"/>
</linearGradient>
<!-- section separator -->
<linearGradient id="g-sep-amber" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#fbbf24" stop-opacity="0"/><stop offset="15%" stop-color="#fbbf24" stop-opacity="0.3"/>
<stop offset="85%" stop-color="#fbbf24" stop-opacity="0.3"/><stop offset="100%" stop-color="#fbbf24" stop-opacity="0"/>
</linearGradient>
<linearGradient id="g-sep-blue" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#60a5fa" stop-opacity="0"/><stop offset="15%" stop-color="#60a5fa" stop-opacity="0.3"/>
<stop offset="85%" stop-color="#60a5fa" stop-opacity="0.3"/><stop offset="100%" stop-color="#60a5fa" stop-opacity="0"/>
</linearGradient>
<!-- token bucket fill -->
<linearGradient id="g-bucket" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#1e3a5f" stop-opacity="0.6"/><stop offset="100%" stop-color="#0f172a" stop-opacity="0.8"/>
</linearGradient>
</defs>
<style>
.bg { fill: #0f172a; }
.title { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 26px; font-weight: 700; fill: #f1f5f9; }
.subtitle { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 12px; font-weight: 400; fill: #94a3b8; }
.section-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 600; letter-spacing: 1.5px; text-transform: uppercase; }
.label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 600; fill: #94a3b8; }
.label-sm { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 9px; font-weight: 500; fill: #94a3b8; }
.desc { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 400; fill: #cbd5e1; }
.desc-sm { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10px; font-weight: 400; fill: #cbd5e1; }
.code { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 10.5px; fill: #e2e8f0; }
.code-sm { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 9px; fill: #e2e8f0; }
.note { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10px; fill: #64748b; }
.panel-title { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; }
.panel-code { font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace; font-size: 10px; fill: #e2e8f0; }
.panel-desc { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10px; fill: #94a3b8; }
.warn { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 10px; font-weight: 600; fill: #f472b6; }
.quote { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 11px; font-weight: 500; fill: #f1f5f9; font-style: italic; }
.mini-label { font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; font-size: 9px; font-weight: 600; }
</style>
<!-- background -->
<g opacity="0.05">
<pattern id="dots" width="40" height="40" patternUnits="userSpaceOnUse">
<circle cx="20" cy="20" r="0.8" fill="#94a3b8"/>
</pattern>
<rect width="1200" height="620" fill="url(#dots)"/>
</g>
<!-- ambient glows -->
<rect width="1200" height="620" fill="url(#glow-top)"/>
<rect width="1200" height="620" fill="url(#glow-bot)"/>
<!-- ================================================================ -->
<!-- TITLE -->
<!-- ================================================================ -->
<g filter="url(#title-glow)">
<text class="title" x="420" y="38" text-anchor="middle" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="26px" font-weight="700" fill="#f1f5f9">Go Timers &amp; Rate Limiting</text>
</g>
<text class="subtitle" x="420" y="58" text-anchor="middle" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="12px" font-weight="400" fill="#94a3b8">time.After · Ticker · rate.Limiter · timeouts</text>
<!-- ================================================================ -->
<!-- SECTION: TIMERS (y=90-300) -->
<!-- ================================================================ -->
<line x1="52" y1="86" x2="820" y2="86" stroke="url(#g-sep-amber)" stroke-width="1"/>
<text class="section-label" x="52" y="100" fill="#fbbf24" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">TIMERS</text>
<!-- ================================================================ -->
<!-- time.After with select (x=52, y=110, w=350) -->
<!-- ================================================================ -->
<g transform="translate(52, 110)">
<!-- timeline bar -->
<rect x="0" y="14" width="340" height="8" rx="4" fill="#1c1c2e" stroke="#f59e0b" stroke-width="1" stroke-opacity="0.5"/>
<rect x="0" y="14" width="230" height="8" rx="4" fill="#f59e0b" fill-opacity="0.15"/>
<!-- "now" and "3s" labels -->
<text class="label-sm" x="0" y="8" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="500">now</text>
<text class="label-sm" x="334" y="8" fill="#fbbf24" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="500">3s</text>
<!-- traveling marker -->
<circle cx="230" cy="18" r="5" fill="url(#g-ball-amber)" filter="url(#shadow)"/>
<!-- pipe opening at end -->
<rect x="340" y="10" width="10" height="16" rx="2" fill="#f59e0b" fill-opacity="0.3" stroke="#fbbf24" stroke-width="0.8"/>
<text class="mini-label" x="355" y="22" fill="#fbbf24" font-size="7" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-weight="600">chan</text>
<!-- code below timeline -->
<text class="code" x="0" y="46" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10.5px" fill="#e2e8f0">&lt;-time.After(3*time.Second)</text>
<!-- select diamond -->
<polygon points="170,68 195,80 170,92 145,80" fill="none" stroke="#94a3b8" stroke-width="1.2"/>
<text class="mini-label" x="170" y="83" text-anchor="middle" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">select</text>
<!-- path 1: channel fires first (teal) -->
<line x1="145" y1="80" x2="50" y2="80" stroke="#2dd4bf" stroke-width="1.2" stroke-dasharray="4,3"/>
<line x1="50" y1="80" x2="50" y2="110" stroke="#2dd4bf" stroke-width="1.2" marker-end="url(#arr-teal)"/>
<rect x="10" y="114" width="140" height="22" rx="4" fill="#14b8a6" fill-opacity="0.1" stroke="#14b8a6" stroke-width="0.8"/>
<text class="code-sm" x="80" y="129" text-anchor="middle" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">result received</text>
<!-- path 2: timer fires first (amber) -->
<line x1="195" y1="80" x2="290" y2="80" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="4,3"/>
<line x1="290" y1="80" x2="290" y2="110" stroke="#fbbf24" stroke-width="1.2" marker-end="url(#arr-amber)"/>
<rect x="230" y="114" width="120" height="22" rx="4" fill="#f59e0b" fill-opacity="0.1" stroke="#f59e0b" stroke-width="0.8"/>
<text class="code-sm" x="290" y="129" text-anchor="middle" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">timeout</text>
<!-- case labels -->
<text class="mini-label" x="20" y="72" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">case &lt;-ch:</text>
<text class="mini-label" x="240" y="72" fill="#fbbf24" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">case &lt;-timer:</text>
</g>
<!-- ================================================================ -->
<!-- time.Ticker (x=440, y=110, w=380) -->
<!-- ================================================================ -->
<g transform="translate(440, 110)">
<!-- main timeline -->
<rect x="0" y="14" width="370" height="4" rx="2" fill="#1c1c2e" stroke="#f59e0b" stroke-width="0.8" stroke-opacity="0.5"/>
<!-- tick marks and pulses -->
<g>
<line x1="0" y1="8" x2="0" y2="24" stroke="#fbbf24" stroke-width="1.5"/>
<circle cx="0" cy="34" r="3" fill="#fbbf24" opacity="0.8"/>
<line x1="62" y1="8" x2="62" y2="24" stroke="#fbbf24" stroke-width="1.5"/>
<circle cx="62" cy="34" r="3" fill="#fbbf24" opacity="0.7"/>
<line x1="124" y1="8" x2="124" y2="24" stroke="#fbbf24" stroke-width="1.5"/>
<circle cx="124" cy="34" r="3" fill="#fbbf24" opacity="0.6"/>
<line x1="186" y1="8" x2="186" y2="24" stroke="#fbbf24" stroke-width="1.5"/>
<circle cx="186" cy="34" r="3" fill="#fbbf24" opacity="0.5"/>
<line x1="248" y1="8" x2="248" y2="24" stroke="#fbbf24" stroke-width="1.5"/>
<circle cx="248" cy="34" r="3" fill="#fbbf24" opacity="0.4"/>
<line x1="310" y1="8" x2="310" y2="24" stroke="#fbbf24" stroke-width="1.5"/>
<circle cx="310" cy="34" r="3" fill="#fbbf24" opacity="0.3"/>
</g>
<!-- ticker.C label on pulses -->
<text class="mini-label" x="155" y="50" text-anchor="middle" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">ticker.C events</text>
<!-- Stop() at end -->
<g transform="translate(350, 10)">
<line x1="-4" y1="-4" x2="4" y2="4" stroke="#f472b6" stroke-width="2"/>
<line x1="4" y1="-4" x2="-4" y2="4" stroke="#f472b6" stroke-width="2"/>
</g>
<text class="code-sm" x="365" y="28" fill="#f472b6" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">Stop()</text>
<!-- code below -->
<text class="code" x="0" y="70" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10.5px" fill="#e2e8f0">time.NewTicker(500*time.Millisecond)</text>
<!-- for/select loop diagram -->
<text class="mini-label" x="0" y="90" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">for { select { ... } }</text>
<!-- select diamond -->
<polygon points="185,108 210,120 185,132 160,120" fill="none" stroke="#94a3b8" stroke-width="1.2"/>
<text class="mini-label" x="185" y="123" text-anchor="middle" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">select</text>
<!-- case ticker.C -->
<line x1="160" y1="120" x2="70" y2="120" stroke="#fbbf24" stroke-width="1.2" stroke-dasharray="4,3"/>
<line x1="70" y1="120" x2="70" y2="148" stroke="#fbbf24" stroke-width="1.2" marker-end="url(#arr-amber)"/>
<text class="mini-label" x="40" y="112" fill="#fbbf24" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">case &lt;-ticker.C:</text>
<rect x="30" y="152" width="110" height="20" rx="4" fill="#f59e0b" fill-opacity="0.1" stroke="#f59e0b" stroke-width="0.8"/>
<text class="code-sm" x="85" y="166" text-anchor="middle" fill="#fbbf24" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">do work</text>
<!-- case ctx.Done -->
<line x1="210" y1="120" x2="300" y2="120" stroke="#2dd4bf" stroke-width="1.2" stroke-dasharray="4,3"/>
<line x1="300" y1="120" x2="300" y2="148" stroke="#2dd4bf" stroke-width="1.2" marker-end="url(#arr-teal)"/>
<text class="mini-label" x="230" y="112" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">case &lt;-ctx.Done():</text>
<rect x="250" y="152" width="110" height="20" rx="4" fill="#14b8a6" fill-opacity="0.1" stroke="#14b8a6" stroke-width="0.8"/>
<text class="code-sm" x="305" y="166" text-anchor="middle" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">return</text>
</g>
<!-- ================================================================ -->
<!-- SECTION: RATE LIMITING (y=320-580) -->
<!-- ================================================================ -->
<line x1="52" y1="316" x2="820" y2="316" stroke="url(#g-sep-blue)" stroke-width="1"/>
<text class="section-label" x="52" y="330" fill="#60a5fa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="600" letter-spacing="1.5px">RATE LIMITING</text>
<!-- ================================================================ -->
<!-- Token Bucket (x=52, y=340, w=380) -->
<!-- ================================================================ -->
<g transform="translate(52, 345)">
<!-- bucket shape -->
<path d="M60,10 L60,170 Q60,185 75,185 L245,185 Q260,185 260,170 L260,10 Q260,0 245,0 L75,0 Q60,0 60,10 Z" fill="url(#g-bucket)" stroke="#3b82f6" stroke-width="1.2"/>
<!-- capacity label -->
<text class="label-sm" x="270" y="95" fill="#60a5fa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="500">burst: 30</text>
<line x1="262" y1="5" x2="275" y2="5" stroke="#60a5fa" stroke-width="0.8" stroke-dasharray="2,2"/>
<line x1="262" y1="183" x2="275" y2="183" stroke="#60a5fa" stroke-width="0.8" stroke-dasharray="2,2"/>
<line x1="275" y1="5" x2="275" y2="183" stroke="#60a5fa" stroke-width="0.8" stroke-dasharray="2,2"/>
<!-- tokens inside bucket (5 of 10 shown, scattered) -->
<circle cx="100" cy="155" r="6" fill="url(#g-ball-teal)"/>
<circle cx="120" cy="150" r="6" fill="url(#g-ball-teal)"/>
<circle cx="140" cy="158" r="6" fill="url(#g-ball-teal)"/>
<circle cx="160" cy="152" r="6" fill="url(#g-ball-teal)"/>
<circle cx="180" cy="160" r="6" fill="url(#g-ball-teal)"/>
<!-- empty slots (ghost tokens) -->
<circle cx="200" cy="155" r="6" fill="none" stroke="#14b8a6" stroke-width="0.6" stroke-dasharray="2,2" opacity="0.3"/>
<circle cx="220" cy="150" r="6" fill="none" stroke="#14b8a6" stroke-width="0.6" stroke-dasharray="2,2" opacity="0.3"/>
<circle cx="130" cy="135" r="6" fill="none" stroke="#14b8a6" stroke-width="0.6" stroke-dasharray="2,2" opacity="0.3"/>
<circle cx="170" cy="138" r="6" fill="none" stroke="#14b8a6" stroke-width="0.6" stroke-dasharray="2,2" opacity="0.3"/>
<circle cx="150" cy="130" r="6" fill="none" stroke="#14b8a6" stroke-width="0.6" stroke-dasharray="2,2" opacity="0.3"/>
<!-- drip arrows from top (steady rate) -->
<text class="label-sm" x="160" y="-18" text-anchor="middle" fill="#60a5fa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="500">rate: 10/s</text>
<line x1="120" y1="-8" x2="120" y2="4" stroke="#60a5fa" stroke-width="1" marker-end="url(#arr-blue)" opacity="0.7"/>
<line x1="145" y1="-8" x2="145" y2="4" stroke="#60a5fa" stroke-width="1" marker-end="url(#arr-blue)" opacity="0.7"/>
<line x1="170" y1="-8" x2="170" y2="4" stroke="#60a5fa" stroke-width="1" marker-end="url(#arr-blue)" opacity="0.7"/>
<line x1="195" y1="-8" x2="195" y2="4" stroke="#60a5fa" stroke-width="1" marker-end="url(#arr-blue)" opacity="0.7"/>
<!-- consume from bottom -->
<line x1="160" y1="186" x2="160" y2="206" stroke="#2dd4bf" stroke-width="1.2" marker-end="url(#arr-teal)"/>
<text class="mini-label" x="160" y="220" text-anchor="middle" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">request proceeds</text>
<!-- request arriving on left -->
<rect x="0" y="80" width="50" height="22" rx="4" fill="#3b82f6" fill-opacity="0.12" stroke="#3b82f6" stroke-width="0.8"/>
<text class="mini-label" x="25" y="95" text-anchor="middle" fill="#60a5fa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">req</text>
<line x1="50" y1="91" x2="63" y2="91" stroke="#60a5fa" stroke-width="1.2" marker-end="url(#arr-blue)"/>
<!-- waiting request -->
<rect x="0" y="112" width="50" height="22" rx="4" fill="none" stroke="#3b82f6" stroke-width="0.8" stroke-dasharray="3,3" opacity="0.4"/>
<text class="mini-label" x="25" y="127" text-anchor="middle" fill="#64748b" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">wait</text>
<!-- code -->
<text class="code" x="0" y="248" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10.5px" fill="#e2e8f0">rate.NewLimiter(10, 30)</text>
</g>
<!-- ================================================================ -->
<!-- Ticker-Based Limiter (x=440, y=340, w=380) -->
<!-- ================================================================ -->
<g transform="translate(440, 345)">
<!-- timeline / gate mechanism -->
<rect x="0" y="0" width="380" height="4" rx="2" fill="#1c1c2e" stroke="#f59e0b" stroke-width="0.8" stroke-opacity="0.5"/>
<!-- tick marks on timeline -->
<line x1="0" y1="-4" x2="0" y2="10" stroke="#fbbf24" stroke-width="1.2"/>
<line x1="76" y1="-4" x2="76" y2="10" stroke="#fbbf24" stroke-width="1.2"/>
<line x1="152" y1="-4" x2="152" y2="10" stroke="#fbbf24" stroke-width="1.2"/>
<line x1="228" y1="-4" x2="228" y2="10" stroke="#fbbf24" stroke-width="1.2"/>
<line x1="304" y1="-4" x2="304" y2="10" stroke="#fbbf24" stroke-width="1.2"/>
<line x1="380" y1="-4" x2="380" y2="10" stroke="#fbbf24" stroke-width="1.2"/>
<!-- conveyor belt -->
<rect x="0" y="30" width="380" height="30" rx="4" fill="#1e293b" stroke="#334155" stroke-width="0.8"/>
<!-- gate -->
<rect x="185" y="22" width="10" height="46" rx="2" fill="#f59e0b" fill-opacity="0.3" stroke="#fbbf24" stroke-width="1"/>
<text class="mini-label" x="190" y="80" text-anchor="middle" fill="#fbbf24" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">&lt;-throttle.C</text>
<!-- queued requests on left -->
<rect x="20" y="36" width="36" height="18" rx="3" fill="#3b82f6" fill-opacity="0.15" stroke="#3b82f6" stroke-width="0.8"/>
<text class="mini-label" x="38" y="49" text-anchor="middle" fill="#60a5fa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">req</text>
<rect x="64" y="36" width="36" height="18" rx="3" fill="#3b82f6" fill-opacity="0.15" stroke="#3b82f6" stroke-width="0.8"/>
<text class="mini-label" x="82" y="49" text-anchor="middle" fill="#60a5fa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">req</text>
<rect x="108" y="36" width="36" height="18" rx="3" fill="none" stroke="#3b82f6" stroke-width="0.8" stroke-dasharray="3,3" opacity="0.4"/>
<text class="mini-label" x="126" y="49" text-anchor="middle" fill="#64748b" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">req</text>
<rect x="152" y="36" width="36" height="18" rx="3" fill="none" stroke="#3b82f6" stroke-width="0.8" stroke-dasharray="3,3" opacity="0.3"/>
<text class="mini-label" x="170" y="49" text-anchor="middle" fill="#64748b" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">req</text>
<!-- request passing through gate -->
<rect x="210" y="36" width="36" height="18" rx="3" fill="#14b8a6" fill-opacity="0.2" stroke="#14b8a6" stroke-width="0.8"/>
<text class="mini-label" x="228" y="49" text-anchor="middle" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">req</text>
<line x1="248" y1="45" x2="285" y2="45" stroke="#2dd4bf" stroke-width="1.2" marker-end="url(#arr-teal)"/>
<!-- processed -->
<rect x="290" y="36" width="70" height="18" rx="3" fill="#14b8a6" fill-opacity="0.08" stroke="#14b8a6" stroke-width="0.6"/>
<text class="mini-label" x="325" y="49" text-anchor="middle" fill="#2dd4bf" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">processed</text>
<!-- gate opens annotation -->
<line x1="190" y1="14" x2="152" y2="14" stroke="#fbbf24" stroke-width="0.8" stroke-dasharray="3,2"/>
<text class="mini-label" x="130" y="18" text-anchor="end" fill="#fbbf24" font-size="8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-weight="600">gate opens each tick</text>
<!-- comparison labels -->
<g transform="translate(0, 100)">
<rect x="0" y="0" width="180" height="44" rx="6" fill="#f59e0b" fill-opacity="0.06" stroke="#f59e0b" stroke-width="0.6"/>
<text class="mini-label" x="10" y="16" fill="#fbbf24" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">Ticker-based</text>
<text class="desc-sm" x="10" y="30" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="400">simple but no burst</text>
<text class="label-sm" x="195" y="22" fill="#64748b" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="500">vs</text>
<rect x="210" y="0" width="170" height="44" rx="6" fill="#3b82f6" fill-opacity="0.06" stroke="#3b82f6" stroke-width="0.6"/>
<text class="mini-label" x="220" y="16" fill="#60a5fa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="9px" font-weight="600">Token bucket</text>
<text class="desc-sm" x="220" y="30" fill="#94a3b8" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="400">allows burst capacity</text>
</g>
<!-- code -->
<text class="code" x="0" y="170" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10.5px" fill="#e2e8f0">for req := range requests {</text>
<text class="code" x="0" y="184" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10.5px" fill="#e2e8f0"> &lt;-throttle.C // rate limit</text>
<text class="code" x="0" y="198" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10.5px" fill="#e2e8f0"> process(req)</text>
<text class="code" x="0" y="212" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10.5px" fill="#e2e8f0">}</text>
</g>
<!-- ================================================================ -->
<!-- RIGHT PANEL (x=860, y=88, w=310) -->
<!-- ================================================================ -->
<!-- accent stripe -->
<rect x="856" y="88" width="3" height="520" rx="1.5" fill="url(#g-accent)"/>
<!-- panel background -->
<rect x="860" y="88" width="310" height="520" rx="8" fill="url(#g-panel)" filter="url(#shadow)"/>
<!-- Timer Types -->
<text class="panel-title" x="880" y="116" fill="#fbbf24" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="700" letter-spacing="1px">Timer Types</text>
<!-- time.After -->
<text class="panel-code" x="880" y="140" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" fill="#e2e8f0">time.After(d)</text>
<text class="panel-desc" x="880" y="154" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#94a3b8">one-shot, returns &lt;-chan Time</text>
<!-- time.NewTimer -->
<text class="panel-code" x="880" y="176" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" fill="#e2e8f0">time.NewTimer(d)</text>
<text class="panel-desc" x="880" y="190" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#94a3b8">one-shot, resettable</text>
<!-- time.NewTicker -->
<text class="panel-code" x="880" y="212" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="10px" fill="#e2e8f0">time.NewTicker(d)</text>
<text class="panel-desc" x="880" y="226" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" fill="#94a3b8">periodic, must Stop()</text>
<!-- divider -->
<line x1="880" y1="244" x2="1150" y2="244" stroke="#1e293b" stroke-width="1"/>
<!-- Gotcha -->
<g filter="url(#glow-rose)">
<text class="panel-title" x="880" y="266" fill="#f472b6" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="700" letter-spacing="1px">⚠ Gotcha</text>
</g>
<text class="warn" x="880" y="284" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="600" fill="#f472b6">time.After in a loop leaks</text>
<text class="warn" x="880" y="298" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="600" fill="#f472b6">timers — use time.NewTimer</text>
<text class="warn" x="880" y="312" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="10px" font-weight="600" fill="#f472b6">and Reset() instead</text>
<!-- bad example -->
<rect x="880" y="324" width="270" height="52" rx="4" fill="#ec4899" fill-opacity="0.06" stroke="#ec4899" stroke-width="0.6"/>
<text class="code-sm" x="890" y="340" fill="#f472b6" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">// leaks! new timer each iteration</text>
<text class="code-sm" x="890" y="354" fill="#f472b6" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">for { select {</text>
<text class="code-sm" x="890" y="368" fill="#f472b6" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px"> case &lt;-time.After(d): ...</text>
<!-- good example -->
<rect x="880" y="384" width="270" height="52" rx="4" fill="#14b8a6" fill-opacity="0.06" stroke="#14b8a6" stroke-width="0.6"/>
<text class="code-sm" x="890" y="400" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">// correct: reuse timer</text>
<text class="code-sm" x="890" y="414" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">t := time.NewTimer(d)</text>
<text class="code-sm" x="890" y="428" fill="#2dd4bf" font-family="'JetBrains Mono', 'SF Mono', 'Fira Code', monospace" font-size="9px">for { select { case &lt;-t.C: t.Reset(d)</text>
<!-- divider -->
<line x1="880" y1="450" x2="1150" y2="450" stroke="#1e293b" stroke-width="1"/>
<!-- Key Insight quote box -->
<text class="panel-title" x="880" y="472" fill="#60a5fa" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="700" letter-spacing="1px">Key Insight</text>
<rect x="880" y="482" width="270" height="80" rx="6" fill="#3b82f6" fill-opacity="0.06" stroke="#3b82f6" stroke-width="0.6"/>
<line x1="880" y1="482" x2="880" y2="562" stroke="#3b82f6" stroke-width="2"/>
<text class="quote" x="892" y="502" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="500" fill="#f1f5f9" font-style="italic">"In Go, time is just another</text>
<text class="quote" x="892" y="518" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="500" fill="#f1f5f9" font-style="italic">channel. Timeouts, tickers,</text>
<text class="quote" x="892" y="534" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="500" fill="#f1f5f9" font-style="italic">and deadlines all compose</text>
<text class="quote" x="892" y="550" font-family="'Inter', 'SF Pro Display', system-ui, sans-serif" font-size="11px" font-weight="500" fill="#f1f5f9" font-style="italic">naturally with select."</text>
</svg>

After

Width:  |  Height:  |  Size: 31 KiB