Files
alkgit/docs/architecture/decisions/009-bounded-resources-budget.md
glm-5.3-flash d067cf558a docs(arch): A-2 + A-6 — async-trait trait family, pinned execution model
Resolves review 001 findings A-2 (critical) and A-6 (major) — the same
signature surface:

- ADR-012 §1: registry traits amended to #[async_trait] (bare async fn
  in traits is not dyn-compatible, E0038; ops sit behind Arc<dyn
  GitRegistryStore>). Desugared boxed Future form pinned in OQ-03's
  freeze inventory. async-trait = "0.1" added to the manifest.
- backend.md concurrency model: the five-trait family is
  #[async_trait] Send + Sync dyn-compatible; the wire layer enforces
  ADR-009's pipeline-concurrency budget itself (permit acquired around
  each GitPackGen/GitPackIngest call — the concrete admission point);
  impls must not block the async executor and own their internal
  threading (gix impls run spawn_blocking inside the impl — POC-2's
  shape restated at its true layer).
- transport.md §fetch: spawn_blocking sentence rephrased to the
  trait-contract version (the wire spec stops speaking gix).
- ADR-009: enforcement point of the blocking-pool budget made concrete.
- ADR-013 §6: ingestion spawn_blocking line aligned.
- review 001: A-2, A-6 marked resolved.

Verification: cargo test, clippy -D warnings, fmt --check, doc
--no-deps, check --no-default-features, check --all-features — all
clean.
2026-09-29 08:29:46 +00:00

3.5 KiB
Raw Permalink Blame History

ADR-009: Bounded-resources budget model

Status

Accepted

Context

Git servers are internet-facing by definition; unbounded loops and buffers are bugs. Each POC surfaced specific unbounded surfaces that need budgets:

  • Negotiation rounds / haves count (fetch can loop forever without done).
  • receive-pack POST body size (push can be arbitrarily large; alkhttp custom routes get hyper's unbounded stream — POC-3).
  • Session wall-clock (long-lived ssh/git sessions).
  • Blocking-pool usage: pack generation runs on spawn_blocking; a thundering herd of fetches can starve the pool (POC-2 follow-up 2).
  • Sideband chunk size is bounded (65000) but max pack size per fetch is still unbounded above it.
  • alkcall channels carry their own backpressure limits (alkcall ADR-040); git sessions ride raw duplex streams, so those limits do not automatically apply.

Decision

Every session carries a Limits value, constructed by the door/adapter from assembler config and handed to the wire layer as part of the session tuple (ADR-002). On the native path the producer adapter is in-crate (GitAdapter), but Limits still originates from assembler config — the adapter's constructor takes it. Defaults are crate constants; overrides are assembler config.

Budget Applies to Default direction
max negotiation rounds fetch (V2, no done) tens
max haves per round fetch thousands (the client's stateless doubling can legitimately reach 16384 — set the default at or above that)
max pack size receive-pack config-bound (tens of MB v1)
max request body http POSTs (receive-pack especially) same as max pack size
session wall clock all sessions (enforced by transport's session loop on every door — it is the one component all doors hand the session to; alkcall channel caps add a second bound where channels exist) tens of minutes
max concurrent blocking pipeline tasks server-wide (blocking-pool budget; covers pack generation and pack ingestion alike — both are spawn_blocking consumers) small count
sideband chunk size fetch streaming 65000 (fixed, per protocol)
max advertisement refs ls-refs response config-bound

On breach: the session ends with a substrate-appropriate error — pkt-line error band + close on duplex; on http, client-fault budgets (request body size) map to 413, server/session budgets (wall clock, rounds, generation concurrency exhaustion) map to 503. Budgets are fail-closed.

Max pack size on fetch is not budgeted in v1 (the pack is a function of the repo, not the request); receive-pack is the untrusted-input path and gets the hard cap.

Consequences

  • No adapter can forget a budget: transport refuses to start a session without Limits (part of the tuple, ADR-002).
  • Streaming stays O(counts) regardless of budgets; budgets bound aggregate work, not internal buffering.
  • The blocking-pool budget is enforced at assembly/acceptance time (reject/slow-path excess concurrent generations), not per-byte. Enforcement point (review 001 A-6): the wire layer — a permit acquired around each GitPackGen/GitPackIngest call; impls own their internal spawn_blocking (backend.md concurrency model).

References

  • docs/research/vision.md §"Guiding principles" 7
  • docs/research/poc2-findings.md follow-ups 2–3; docs/research/poc3-findings.md follow-up 3
  • alkcall ADR-040 (channel backpressure — the thing git sessions bypass)
  • ADR-002 (session tuple), transport.md §Limits