Skip to content

Queue every compile at the fleet leader against per-machine slot and memory budgets - #1737

Merged
christianparpart merged 70 commits into
masterfrom
feature/1728-fleet-compile-queue
Oct 29, 2026
Merged

christianparpart merged 70 commits into
masterfrom
feature/1728-fleet-compile-queue

Conversation

@christianparpart

@christianparpart christianparpart commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Closes #1728.
Closes #1736.

Why

Several builds side by side on one workstation run hundreds of compiles at once. The kernel's OOM killer then takes down unrelated builds: 18 kills in 90 minutes, as measured in #1728. Every one of those compiles already goes through pace-cc, but until now pace could only say "compile it yourself".

What

Every compile that misses the cache now waits its turn in one queue on the scheduling leader. The leader admits work against each machine's slot and memory budget:

  • Leader (Distributed::AdmissionQueue, AdmissionResponder):
    • First come, first served, with backfill: a later compile that fits may start ahead of a large one waiting at the head of the queue.
    • Head reservation: memory freed while the head waits is set aside for it, so it cannot be starved.
    • Over-budget compiles run alone.
    • Dedup by STORE: concurrent misses of one key on one machine compile once, and the waiters FETCH the stored result.
    • The queued request is held open and pulses Status::Progress, so the wait is never silent.
    • The connection is the lease: a killed client releases its charge when its socket closes.
    • Failover: a new leader rebuilds its ledger from each machine's report, using max(charged, held).
  • Every non-leader node (follower or learner) relays Admit/Settle to the leader inline (AdmitForwarder), and never to itself.
  • Every node sends a MachineBudget report each round: budget, held charges and measured peaks. A node with --slots=0 sends one too, and that is not a worker registration. New flags:
    • --machine-slots
    • --machine-memory-budget (default 75% of RAM minus the cache tier)
    • --compile-estimate-default (default 2 GiB)
  • Launcher (pace-cc, MissFlow / AdmitSession):
    • Every miss is admitted at the local node before compiling locally or dispatching. The admission connection stays open on its own thread for the whole compile.
    • STORE happens before SETTLE.
    • A worker refusal re-queues the same ticket on the same connection.
    • Falls back to today's behaviour when no node answers, and skips the admission dial once a FETCH has already found the node unreachable.
  • Peak memory is measured per compile, children included (wait4 on POSIX, a job object on Windows), and feeds the estimates. Estimates are keyed by cache key, then by (source path, compiler).
  • Observability:
    • pace-cc -s shows the queue wait and the peak memory.
    • /metrics gets admission counters, a real wait _sum/_count, and per-machine budget and in-use gauges.
    • pacectl live-stats fleet, /fleet.json and the fleet page show the queue depth and the clients (machine, build tree, running, queued).
  • 0xFC wire v16: new Admit, Settle and MachineBudget verbs. Status::Progress is allowed on {Compile, Admit}.
  • core-cpp 0.6.0 → 0.11.0.
  • TCP keepalive at both ends of a held admission connection. The launcher and relay dials already armed it. Now the leader arms it on the accepted connection once that connection holds a grant (IConnectionHold::KeepAlive(), once per connection). A caller whose host vanishes without a FIN is noticed in about 16 s (30 s on Windows), not after the one-hour lease sweep. Every pace socket decorator forwards armKeepAlive.
  • Rulebook: a new "compile queue" section in .agent/rules/distributed-compilation.md, tripwires in AGENT.md, operator docs for pace-cc, pace-node and pacectl, and the protocol grammar.

Decisions made along the way (recorded in the rules)

  • A dispatchable compile tries the workers with room, then runs locally if this machine has room, so cores never idle while capacity exists.
  • Admission always goes to the local node (PACE_ADDR). PACE_SCHEDULER no longer redirects it, and dispatch planning no longer requires it.
  • An admission Worker grant files its lease per ticket. Dedup is the queue's job.
  • A Local grant of a dispatchable compile carries its reason, so "no worker serves this toolchain" is still reported and counted.
  • After MaxWorkerRefusals (3) the compile runs locally, still charged. A worker failure settles NotStored, never an abandon, so estimates aren't poisoned.

Compatibility

0xFC v15 ↔ v16 do not interoperate (MinSupportedVersion = 16). Raft (v5) and the on-disk state are unchanged, so an upgraded node keeps its identity and cluster state. In a fleet mixing 0.6.0 and this version, registration, dispatch and pacectl fail between old and new machines; every machine still compiles locally and uses its own cache. Upgrade every member together.

Known limitations (each filed)

Tested

Still to be done by the owner after merge: the acceptance run on the 32-core workstation (six concurrent cmake --build, zero OOM kills).

)

CompileRun gains peakMemoryBytes, disengaged when nothing was measured.
POSIX reaps with wait4 and reads ru_maxrss (scale in MaxRssUnitBytes);
Windows spawns suspended into a job object and reads
PeakProcessMemoryUsed. A failed job call only costs the measurement.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
The invocation log gains queued-ms and peak-memory-bytes columns (format
v3, so v2 lines are counted as unreadable), and `pace-cc -s` prints the
total slot wait and the largest peak compile memory when any record
carries them. Absent readings stay absent.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…#1728)

docs/tools/pace-cc.md named the v2 layout; it now describes v3 with the
queued-ms and peak-memory-bytes columns. Tally::peakMemoryMax becomes an
optional (absent is not zero), which also removes a bool between
uint64_t members, and the exit-code and unsigned readers fold into one
ParseOptionalInteger template.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ape (#1728)

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…1728)

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…#1728)

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Formatting only: constructor initializer-list line wraps in
PeakEstimates.cpp. No token changes.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ill (#1728)

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…n on (#1728)

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…r reset (#1728)

Settle and Abandon replace Release; LoseLeadership returns every parked ticket.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… dedup owners (#1728)

A refused ticket re-parked at the front broke Requeue's sorted-by-ticket
assumption; entries now carry an order key. Dedup owners are looked up by
cacheKey. Adds tests for the refused-machine local caller and for woken,
undelivered Stored waiters.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
ADMIT (0x22), SETTLE (0x23) and MACHINE-BUDGET (0x24) with their owning
decoded forms, EstimateKeyFields, and byte pins for the three admission
enums. Wire version and floor move to 16 together because ADMIT may be
answered with Status::Progress; ProgressIsCompileOnly becomes
ProgressVerbsAre({Compile, Admit}). MACHINE-BUDGET is ProvenNodeOnly.

The daemon and a node running no consensus refuse all three as capacity
verbs (DispatchNotPermitted); a scheduler answers the same until the
leader serves them.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…w follow-ups (#1728)

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ort (#1728)

Every node states its machine's compile budget to the fleet's queue on the
presence loop's round, whether or not it runs a worker (R6):

- --machine-slots, --machine-memory-budget (size or N% of RAM) and
  --compile-estimate-default, restart-only and not worker rows, so a
  --slots=0 node accepts them. MachineBudgetOf derives 75% of RAM less the
  cache tier's reserve, floored at the default estimate.
- IHeldCharges / HeldChargeRegistry (RAII handles) for the leases a node
  grants, summed with CompileCapacity's running compiles.
- Op::Compile carries trailing admission fields (chargedBytes, cacheKey,
  sourcePath); the worker holds the charge with the slot and reports the
  measured peak under {cacheKey, sourcePath, fingerprint}.
- SendMachineBudgetOnce dials through DialAndAnnounce; a refusal is said on
  its transition through SchedulerReachability and the peaks are offered
  again.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…iew follow-ups (#1728)

- WorkerProtocol takes its IJobMeasurementSink as a required argument; every
  other site passes IgnoreJobMeasurements() explicitly.
- A worker-tier case runs an admitted compile through the real wiring and
  asserts the measured peak lands in the ledger.
- The budget dial is filed under its own reachability subject, and is skipped
  in a round whose presence announce reached no scheduler.
- ParseMachineSlots is ParseSlots plus a zero refusal; Doxygen on the registry
  handle; the wire history notes COMPILE's 10 -> 13 fields.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…hed nobody (#1728)

Round 1 gated the budget on the presence being recorded, which starved a node
with an empty advertised endpoint (R6) and let a refused presence suppress
it. The loop now skips only when the presence dial ran and could not reach a
scheduler; an empty endpoint and a refusal still send the budget.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ne budgets (#1728)

AdmissionResponder on the scheduling leader serves ADMIT, SETTLE and
MACHINE-BUDGET. An ADMIT parks on the node's reactor (interruptibleSleepUntil,
so a reactor torn down mid-wait frees the frame), the endpoint pulses
Status::Progress through its per-verb pulse, and the decision wakes it through
a stop token. A grant keeps the connection open and holds its charge on a new
per-connection ConnectionScope: a SETTLE gives it back, a close or any other
frame abandons it, and the hold releases in a destructor. The endpoint's read
watch tells the scope when the peer leaves, and retires itself with CancelRead
(not Close) after a reply that left a hold.

SchedulerService owns the AdmissionQueue, PeakEstimates and a placement source
over WorkerRegistry; each grant re-asks the gate with a fresh caller context
(R9) and a worker grant mints a real signed lease. Demotion calls
LoseLeadership and answers every waiter NotLeader naming the new leader. A
loopback local lease is recorded in HeldChargeRegistry for its lifetime.

Eight admission counters (new SchedulerAdmission surface) and two gauges,
rendered by /metrics and carried by the live-stats snapshot (grammar 7).

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…r returns it to the front (#1728)

SETTLE{WorkerRefused} no longer abandons the ticket the queue just put back:
AdmissionQueue::Settle re-parks the SAME ticket at the front with the refusal
counted (R7), and SettleAdmission now only gives the worker's lease and job
back, keeping the ticket record so the connection's next ADMIT waits on it.

A worker that stops being live between the pump placing on it and the grant
minting its lease is no longer answered NoWorker: GrantFor reports it, and
AdmissionQueue::ReturnToFront puts the ticket back at the front without a
refusal, so the request keeps its whole bound and the machine is not marked
refused.

Service-level cases on a ManualClock cover the worker grant (signed token,
lease, job), settle and abandon releasing both, the refusal keeping the
ticket, AlreadyInFlight, a keyless worker never placed on, and the vanished
worker; WorkerRegistry::Candidates and Find get cases of their own.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…dged by its pulse (#1728)

After SETTLE{WorkerRefused} the connection keeps the put-back ticket as a
RequeuedHold with its waiter registered, so a pump that decides it first has
somewhere to leave the decision; the next ADMIT on that connection binds to
it without a new Enqueue, and any other frame or the close abandons it
(Ruling 9). The AdmissionWorkerRefused help and docs say so.

A parked ADMIT has no total ceiling any more: it parks in steps, and its
answer window (AdmissionIdleBound, 30 s at the default cadence) bounds the
silence between pulses, renewed by the endpoint each time a pulse reaches the
peer the responder asked to be judged that way (ConnectionScope::JudgeByPulse).

One departure is one AdmissionAbandoned: the endpoint marks the scope when it
files an undelivered grant, and the hold that departure releases does not
count it again. The idle window before the next request is the hold's own
(FrameServer::IdleWindowFor), and a retired peer watch is silent only for the
Cancelled its retirement produced (FrameServer::ClassifyWatch), so a reset
resolved before the retirement still tells the scope. The demotion NotLeader
carries its own uncounted rationale, and MetricsSnapshot::admission says the
live-stats frame carries it.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…1728)

A request parked forever is pulsed forever, so a per-read deadline never
fires: a build that lost the requeued ticket hung the case for twenty minutes
instead of failing it. ReadAnswer now gives up after ten seconds in all.
clang rejects the NetError designated initializer that omits its context.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… case that could not fail (#1728)

test-loops refused the pump's for (freed) loop and the park's for (;;), both
now a while. The over-the-wire departure case is removed: under a neuter of
the one-count fix it stayed green in 40 of 40 runs (the endpoint always wrote
the grant before it saw the departure), so it asserted nothing the scope-level
case does not, and a green test on the wrong path retires the suspicion.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… no private membership fake (#1728)

The re-gate case used FlippableMembership, a private IMembershipOracle the
membership-fakes check refuses. The fixture now admits through
Testing::RosterFold (loopback, then the key roster) with each machine's key
presented under its own id, and the re-gate case parks an ADMIT from a REMOTE
caller that proved its key; the forget lands as that key revoked in the roster
while it waits, and the grant is refused NotAMember holding nothing. With the
grant-time Gate neutered the case goes red (a grant is written instead).

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…der, never re-parked (#1728)

A grant outlives a demotion, and SETTLE{WorkerRefused} arriving after it put
the ticket back in a follower's queue, which nothing pumps: the re-ADMIT then
parked for ever behind pulses that kept the connection alive. SettleAdmission
now decides under the admission lock, against a flag the demotion writes in
the same locked step it forgets the queue in, and abandons the ticket there
(SettleResult::NotLeading); the responder drops the put-back hold and answers
NotLeader naming the leader. The requeued re-ADMIT refuses the same way on a
node that no longer leads, as defence in depth, and says why its payload is
not read (Ruling 9).

The pulse-renewal docs say a pulse the kernel ACCEPTED, not one that reached
the peer: a vanished host is bounded by TCP retransmission, not by the
window. The NoteDepartureCounted call site names its test gap.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A node that runs consensus but no scheduler now relays the queue's ADMIT and
SETTLE from a launcher on its own machine to the leader, instead of answering
NotLeader. AdmitForwarder is an IFrameResponder that keeps ONE proven, sealed
upstream connection per client connection, on the client's ConnectionScope for
as long as the leader holds something for it (a grant until its settle, a ticket
a worker refused until the ADMIT that asks again), so any sequence of requests
and every final reply travel in order and a client close closes upstream.

The endpoint keeps writing this node's own pulse to the client; the leader's
pulses bound the upstream silence, so a dead leader behind a healthy pulse is
noticed. An unreachable, unproven, lost or silent leader, or one that moves more
than twice, is answered EndpointBusy (AdmissionForwardFailed) and the launcher
fails open; a NotLeader naming a new leader is followed transparently before the
grant. A forwarded Local grant is recorded in HeldChargeRegistry. Only callers
on this machine are forwarded; every other verb keeps the redirect.

SchedulingRedirectResponder delegates the queue verbs through
ForwardAdmissionWith; ProvenSchedulerDialer dials from the loop and proves this
node's identity key. The idle-window timer moves from ReactorExchange to a shared
header, and the leader's test fixture to AdmissionTestUtils.hpp so both suites
stand up the same fake leader. Counters AdmissionForwarded,
AdmissionForwardFailed and AdmissionForwardAbandoned on a new AdmissionForwarding
surface, served where the node redirects.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Breaking changes in 0.7.0 - 0.10.0 and what pace did about each:

- 0.8.0, Windows paths handed to core-cpp are read as UTF-8: pace passes
  no path to the affected entry points (parseLogFileSpec, homeResolvedPath,
  absolutePath, listDirectory, loadImage, ScopedTempDir); nothing to do.
- 0.8.0, homeResolvedPath/absolutePath/parseLogFileSpec return
  std::expected: pace does not call them; nothing to do.
- 0.9.0, core::log::Sink::console() writes to stderr: pace has its own
  Logger and uses no core::log; no stdout log parsing exists; nothing to do.

The build is clean on cl-debug without source changes. 0.7.0 and 0.10.0
list no Breaking entries. No other pin of core-cpp's version exists
outside CMakeLists.txt.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ide (#1728)

The seam check read only selection blocks spelling std::ranges::<name>, so
core-cpp 0.10.0's Stride (std::views::stride) was not a row. The scope
(ranges or views) is now part of the row, read from the header, so
views::stride, std::views::stride and std::ranges::views::stride are
refused with core::ranges::Stride offered, while std::views::iota stays
legal. The selftest gains a views row in the staged header, three planted
violations and a clean case.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…de (#1728)

std::views::stride is missing from every libc++, so the helper could not
build on a macOS leg. The target links core::async, which is header-only
and carries core-cpp's include path, so no library is added to the link.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…itself (#1728)

Ruling 10: every node that does not lead -- a learner, or a voter
following another -- relays a launcher's ADMIT and SETTLE to the leader
instead of answering NotLeader. The forwarder is built under
ForwardsAdmission (every consensus member) and wired into the scheduler
surface too: an ADMIT is relayed unless this node's queue decides it
(it leads, or the connection proved this machine's own key), and a
connection holding a forwarded link stays with the forwarder whoever
leads by the time it settles. A leader or redirect naming this node's
own announced endpoint is refused NotLeader naming nobody, never dialled.

Review fixes: the upstream silence bound is the wire's own
DefaultCompileIdleTimeout (ForwardPolicy::upstreamIdle), not derived
from this node's pulse; an undecodable grant is refused ForwardFailed
and its link closed; the AdmissionForwarding surface is served under
the predicate the relay is built under, with the prover asserted rather
than guarded; a dial in progress is documented as not cancelled.

Tests: the production ProvenSchedulerDialer against a leader on the
RosterFold behind RelabelledPeerListener (charge keyed to the forwarding
machine, a stranger refused, relabelling control), voter-follower relay
and settle across a leadership change, no-leader refusal, no
self-forwarding (endpoint, redirect, own proven key), a slower-pulsing
leader kept, an undecodable grant, the largest grant's margin.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
A held admission connection is the lease: its charge is given back when it
closes, and a connection to a host that vanished never closes on its own
(Ruling 18). The launcher's admit dial (`Cc::DefaultAdmitBudget`) and the
node's relay to the leader (`Node::ForwardDialBudget::keepAlive`) now ask
for keepalive, per dial as the rulebook requires.

The intervals are core-cpp's `KeepAliveSettings {}`, which every
`KeepAlive::Yes` dial applies -- a dial takes none of its own -- pinned by a
`static_assert` beside the budget (`Cc::AdmitKeepAliveProbe`), because the
vanished-peer bound (16 s on Linux and macOS, 30 s on Windows) is derived
from them. The leader's side of the held connection is an ACCEPTED socket,
which core-cpp gives no way to arm; that half needs a core-cpp API.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…E_SCHEDULER names (#1728)

Ruling 16. The leader charges a compile to the machine that ASKED, and can
name that machine only by a key the connection proved or by loopback: this
machine's node proves its key when it relays, while a launcher dialling a
remote scheduler directly proves nothing and would be charged to no machine.
So `PACE_SCHEDULER` no longer redirects ADMIT; it keeps its one meaning,
where a LEASE goes when no queue answers.

Both decisions are pure functions in MissFlow, so they are cases rather than
comments: `Cc::AdmissionEndpointOf` and `Cc::PlanUnreachableAt` -- a plan is
refused for a remembered-unreachable endpoint only when every place it could
be spent was unreached, and the LEASE fallback is not dialled at a scheduler
a launcher a moment ago could not reach, exactly as before the queue.

The responder's comment no longer claims a ticket names the charged machine,
and the pace-cc page says where ADMIT goes and what a stopped node costs a
passthrough that made no FETCH: one connect per fifteen-second memo window.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…budget is a gauge (#1728)

The wait sum `pace_admission_wait_milliseconds_total` was paired with a
count spelled as the sum of three grant counters, and each grant added the
wait since the request was FIRST enqueued -- so a request a worker refused
added its first wait again on every regrant. It is now a `_sum`/`_count`
pair: `AdmissionWaitCount` (`pace_admission_wait_count_total`, catalogued,
attributed to the scheduler's admission surface, documented) moves once per
grant, and the sum adds the time since the request was last PARKED
(`AdmissionVerdict::waited`). The grant still tells the client the whole
wait (`queued`), which is what the launcher sums.

And spec goal 9 asked for budget beside charge per machine: `/metrics` now
renders `pace_admission_budget_bytes{machine}` and
`pace_admission_in_use_bytes{machine}` for every machine that reported a
budget, from one table, carried by the live-stats codec too (grammar 8; the
layout pin moves with the new counter).

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…1728)

`AdmitSession` printed "settling the admission failed" to stderr itself,
so after a leader failover every compile of a build said it -- a line per
compile, unasked, while the leader reads the connection's end as a release
anyway. The session now takes a required `Cc::AdmitNote` sink, and the
launcher hands it one gated on `PACE_VERBOSE`.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…ing as open (#1728)

`FrameServer::Shutdown()` returns once nothing is open, and `main` destroys
the node's `HeldChargeRegistry` right after the endpoint. The connection task
deregistered itself first and ended its scope's hold second, so a held
admission's charge could be given back to a registry that was already gone.
The hold is now ended before the connection is untracked.

Not covered by a case that discriminates the order: a slow-releasing hold
was tried, and with the order reversed it still passed, because the drain
also waits for the accept and sweep loops, which end on the same reactor
thread behind the release. The hazard needs both loops to have ended before
the connection's own teardown, which no fixture here can arrange.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
#1728)

The leader's launchers reach its queue over loopback and are charged to
`SchedulerService::OwnMachineId`, and the queue admits nothing on a machine
that has not reported. A case now runs the production budget round, dialler
and proof from the leader to itself and shows the budget filed under that
same machine, and a loopback `LocalOnly` request granted against it.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
… rules (#1728)

The compile-queue section states what the final review fixed: the grant
carries the lease grant's dial hint; an admission's worker lease is filed
under its ticket (Ruling 17); a launcher asks the queue at its own node
(Ruling 16); where the queue answered, a launcher that gives up runs the
compile charged here; what a stopped node costs a passthrough with no FETCH;
and the keepalive bound on held admission dials (Ruling 18), with the half
it does not cover yet -- the leader's accepted socket, which core-cpp
cannot arm. Two overlong lines are re-wrapped to the file's width.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
- `WorkerRegistry::JobStarted` has its doc block back; `Candidates`/`Find`
  had been inserted inside it.
- `ProcessRunner.cpp`: `JobHandle` sits above the attribute-list class's
  doc block rather than between it and its class, and `PeakProcessMemory`
  says what it reads -- the peak commit charge of one process of the job.
- `MetricsSurface::AdmissionForwarding` is served on every node that answers
  the scheduling verbs, voters as well as learners (Ruling 10).
- pace-node `main.cpp`: a duplicated comment above the admission relay.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…t the daemon's (#1728)

The daemon page's metrics section is checked against what the daemon
renders; the budget and in-use gauges are node series, documented on the
distributed-compilation page beside the charged one.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
- `DecodeStatsReading` reads the admission block through `ReadAdmission`,
  as it reads consensus through `ReadConsensus`: grammar 8's second list
  took it past the cognitive-complexity threshold.
- Two test helpers: a const-reference parameter, and a regate lambda that
  returns an explicit copy.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
@christianparpart christianparpart added the type/feature New capability, or a user-visible extension of one label Oct 28, 2026
@github-actions github-actions Bot added area/launcher apps/fastcache-cc/ - the compiler launcher and its cache key area/compile-cache CompileCache/, the 0xFC wire, the executor, PathCanon area/distributed Distributed/, apps/fastcache-compile-node/ - dispatch, workers, scheduler area/protocol Protocol/, Auth/ - memcached, RESP, autodetect, the auth gate area/observability Metrics/, logging, stats and scrape surfaces area/packaging packaging/, CPack, deb/rpm/pkg/msi, installers area/build CMake, presets, toolchains, CI workflows labels Oct 28, 2026
…t as a build-dir define (#1728)

The helper's absolute build-tree path was a COMPILE_DEFINITION on
ProcessRunner_test.cpp, so the preprocessed unit differed per build
directory and missed the warm replay build. It now travels in the
test environment; the cases SKIP when it is absent. The trivial-process
case reuses the helper, so no CMAKE_COMMAND path is needed either.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…cOS runner (#1728)

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
ISocket::armKeepAlive, for a server that decides an accepted connection is long-lived.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…nnection (#1736)

A held admission connection is the lease; a caller host that vanished without a FIN kept its
charge until MaxCompileLeaseLifetime. Once a reply leaves the connection holding a grant, the
endpoint arms keepalive through ISocket::armKeepAlive, once per connection, with the intervals
the dials use (Pace::AdmitKeepAliveProbe, moved out of the launcher so one constant names them).
What arms is a column of the hold (IConnectionHold::KeepAlive). SealedFrameSocket and
TotalDeadlineSocket forward the call. A failure to arm is a warning, not a refusal.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
…and that a refusal to arm only warns (#1736)

Pure cases over FrameServer::ArmHeldKeepAlive (nothing held, a hold asking for none, once per
connection; Unsupported warns once and keeps the hold), a forwarded grant armed at the follower
and the leader (the relabelling fake's forward sits under the ledger), and TotalDeadlineSocket
checks the interval too. Rules: name the accepted-socket door in wire-and-protocol.md and make
the wrapper-forwarding sentence true.

Signed-off-by: Christian Parpart <c.parpart@lastrada.net>
@github-actions github-actions Bot added the area/net Net/, Async/, Server/ - sockets, reactors, connections label Oct 28, 2026
@christianparpart
christianparpart merged commit df1b632 into master Oct 29, 2026
61 of 62 checks passed
@christianparpart
christianparpart deleted the feature/1728-fleet-compile-queue branch October 29, 2026 07:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/build CMake, presets, toolchains, CI workflows area/compile-cache CompileCache/, the 0xFC wire, the executor, PathCanon area/distributed Distributed/, apps/fastcache-compile-node/ - dispatch, workers, scheduler area/launcher apps/fastcache-cc/ - the compiler launcher and its cache key area/net Net/, Async/, Server/ - sockets, reactors, connections area/observability Metrics/, logging, stats and scrape surfaces area/packaging packaging/, CPack, deb/rpm/pkg/msi, installers area/protocol Protocol/, Auth/ - memcached, RESP, autodetect, the auth gate type/feature New capability, or a user-visible extension of one

Projects

None yet

1 participant