Repository navigation
Open
Description
sandboxd/pool holds most of a node's state machine in one type. This issue is for discussion: it proposes no change yet.
The shape today
Counted on main at d383d32:
poolhas 9251 production lines in 36 files, 37% of sandboxd's 24742. Its tests are 21883 lines in 58 files.Managerhas 351 methods and 95 fields. Volumes, archives, replication, templates, healing, the journals and the tick loops all hang off it.m.muis taken at 123 sites in 28 files. It guardspools,claimed,archiving,pendingCks,tenantLive,drainingandpoolVersiontogether.
Production lines by area:
| area | files | lines |
|---|---|---|
| Archive and failover | archive, failover, replica, lease, reconcile | 1944 |
| Claims and lifecycle | claim, hibernate, operator, liveness, fork, drain, remove | 1818 |
| Warm pools | pool, refill, watermark, setpools, poolstore, reload | 1813 |
| Capture and templates | checkpoint, template, capture, reader, reclock, digest | 1292 |
| Volumes | volume, volume_mutation, volume_catalog, disk | 1103 |
| Persistence and telemetry | claims, journal, telemetry, stats, view | 851 |
| Tenants and egress | env, egress, tenants | 430 |
Why it may be worth doing
- Following one archive flow means holding
archive.go,failover.go,replica.go,lease.go,reconcile.goandclaim.goat once. Manager's fields state their locking in scattered comments, and related state sits apart: thearchive*,heal*and*Sweepfields are spread through the struct.- The
outboundextraction (outbound: a sandbox's doors, proxy and NIC lock move out of pool into their own package #314) moved a sandbox's doors, proxy and NIC lock out ofpoolbehindoutbound.Host, now 1113 production lines, so a move of this kind has a precedent here.
Why it may not be
- Most areas change a
types.Sandboxunderm.mu, and the invariants span areas: a claim moves between running, hibernated, archived and replicated. A split along file lines can leave one lock shared across packages, which is more coupling, not less. - A large move costs review time and risks losing a rollback ordering that only a test records today.
- An interface call or an extra lock on the claim path has to pay for itself: performance is a product requirement.
Candidates, cheapest first
- Group
Manager's fields into named structs with a stated owner and lock, with no package move. It costs little and documents the locking. - Pure data with no
m.mu: the volume catalog's validation, the watermark maths, the journal and telemetry types. Little risk, little gain. - Tick loops:
Runhand-writes five tickers and their fan-out. A registered list means a new sweep does not touchRun, at the cost of an indirection that may not earn its lines. - Replication and failover behind a small interface, as
outboundwas. The largest gain and the largest risk: it needs an answer onm.mufirst.
To decide
- Is the goal fewer lines to hold in the head, or independently testable packages? They lead to different cuts.
- Must
m.mustay one lock? If so, candidate 4 is off. - Hot-path rule for any split:
claimandreleasekeep their cost, shown by an interleaved A/B.
Any change lands as behaviour-preserving steps, one area per PR.
Metadata
Metadata
Assignees
Labels
No labels
Activity
CMGS commentedon Nov 3, 2026
Direction after measuring the tree with every open audit PR applied (#420, #422, #424, #425, #426, #427, #428, #429, #430, #431 plus a follow-up cleanup). Regex counts, so treat them as approximate. No change lands from this comment.
The shape now
pool: 9440 production lines,Managerwith 362 methods,m.mu.Lockat 130 sites (claims and lifecycle 38, archive and failover 35, volumes 20).m.muin their own body. They hang offManagerfor its fields, not because they hold its lock.Distinct method pairs called across areas, caller on the left:
types.Sandboxfields between them (35 and 39 sites) and sharem.claimed(21 and 19 uses). The recent archive rollback, intent settle and unfence bugs all crossed that line.catalog,volumeRunsandvolumeErrorsare almost only theirs. Their admission still needsm.mu.Manager.Answers to the open questions
m.mu: stays one lock. Claim admission, quota and volume holds rely on its atomicity, and a second lock needs a cross-lock order.Proposal, one PR per step, behaviour-preserving, stop and assess after each
Manager's fields into named structs with a stated owner and lock:volumes,heal,templates,recLocks,failover(fence, lease, replicas, registry),sweeps(the*Sweepatomics),limits(the semaphores).m.heal.pull(...).failover.gointo row, adopt and intent;claim.gointo claim, release and reap;refill.gointo golden and refill.Not proposed
m.mu. The catalog moves in step 2 as a type in this package, which gets most of the gain.Production lines stay about flat, possibly slightly up for the type declarations. This is about reading the code, not about reducing it.
Timing
After the open audit PRs merge, to avoid rebasing every one of them over a mechanical move.
CMGS commentedon Nov 3, 2026
Every PR named above is merged, and #432 is the follow-up cleanup.
#433 lists the windows the audit accepted in claims, warm pools and failover. The steps here keep those behaviours, so a reader does not have to re-derive them during a move.
CMGS commentedon Nov 4, 2026
Status after #438, which ran steps 1 to 3 in part, together, not one PR per step as proposed. Behaviour-preserving moves, proved by sorted-line comparison per commit.
Step 1, fields:
sweepsgroups the single-flight guards;recs(recordLocks),heals(healVetoes) andckMarks(ckMarkers) own their state.Step 2, methods: the record locks, the heal vetoes, the archive delete markers and the volume catalog's holder bookkeeping are now methods of their own types, in this package.
Step 3, files:
refill.gosplits intorefill.go,golden.goandprovision.go;reap.goandarchive_delete.goare new; thepooltype joins its methods inwatermark.go;types.gosplits intoenv.goandvolume.go.Not done:
failover.go(723 lines) into row, adopt and intent.claim.go(664 lines) into claim and release.volumes,templates,failoverandlimits.Hot path: warm claim, 8 interleaved rounds with the order swapped, pinned to one NUMA node: T-B p50 +0.0015 ms [-0.0068, +0.0097], p90 -0.0016 ms [-0.0158, +0.0127]. Clone tier, 6 rounds: T-B p50 -0.311 ms [-3.175, +2.553]. No measurable cost.
The shape now, regex counts: 357
Managermethods, 140m.mu.Locksites, 9757 production lines.Manager's field count depends on how the groups are counted, so it is not compared with the 95 above.This stays open for the steps not done.