fangorn/ex_git_objectstore
public
ref:d5d4b73f3326b7bc6b5d2417ec09ffc4a7c9ce68
perf(receive-pack): drop the throttled-SHA cheat — feed absorb-only, flush verifies (#28)
Honest replacement for the 4 MB throttled SHA-1 check landed in #27. Per docs/PERFORMANCE.md, that throttle was the scheduling-around-the-cost cheat — same big-O as the original O(N²) bug, just a bigger denominator.
## What changed
`feed/2` is now absorb-only: appends bytes to an iolist body, maintains a 20-byte lookahead, never verifies, never decides completeness, never transitions out of `:pack`. Per-chunk cost: O(1) amortized. Total streaming cost: O(N).
`flush/1` is the explicit end-of-stream signal. It hands the materialized buffer to `Pack.Reader.parse/2` once. That parse pass already runs the SHA-1 trailer verification — so the previous ReceivePack-level check was both redundant AND quadratic. Total verify work: one O(N) hash + one parse, period.
Errors (corrupted trailer, truncated body, malformed entries) now flow through Pack.Reader's `{:error, reason}` and surface as a proper `unpack <reason>` line in report-status. State transitions to `:done` so the channel doesn't hang on a closed connection.
## Caller contract change
`feed/2` no longer auto-finalizes. Every transport must call flush at end-of-stream:
- **SSH** (`Anvil.SSH.CLI`) — already wired in anvil#127's `{:eof, channel_id}` handler.
- **HTTP** (`AnvilWeb.GitHttpController.receive_pack`) — needs flush after `read_full_body`. Bundled with the mix.lock bump in the companion anvil PR.
- **Test transports** — `git_daemon.ex` flushes on socket close; the LFS HTTP adapter flushes after one-shot body read. Both updated here.
## Test plan
- [x] 928/0 in the full ex_git_objectstore suite.
- [x] All test files using direct `ReceivePack.feed` updated to follow the new feed-then-flush contract.
- [x] New assertions in `protocol_interop_test` pin the corrupted-trailer + truncated-pack behavior: state transitions to `:done` with `{:error, _}` and reports a structured `unpack` failure, rather than sitting in `:pack` until the channel closes.
- [x] `mix format --check-formatted` clean.
- [x] `mix dialyzer` clean.
## Memory note
Pack body is still fully buffered in memory until flush. Tracked in anvil#153 — the path forward is a streaming Pack.Reader that consumes bytes and emits parsed objects incrementally. For ovs (~106 MB pack) full-buffer fits comfortably under the 3.82 GiB container cap.
SHA:
d5d4b73f3326b7bc6b5d2417ec09ffc4a7c9ce68
Author:
Anvil <noreply@anvil.fangorn.io>
Date:
2026-05-06 05:24
Parents:
9d4658e
8 files changed
+200
-202
| Type | ||
|---|---|---|
|
|
test/support/git_daemon.ex | +23 −12 |
|
||
|
|
test/support/lfs_http_adapter.ex | +3 −1 |
|
||