Files
alkgit/docs/architecture/decisions/009-bounded-resources-budget.md
T
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

69 lines
3.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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