Skip to content

fix: harden pack publication and fresh-reader fetches - #29

Open
Xe wants to merge 3 commits into
mainfrom
fix/issue-28-pack-visibility
Open

Xe wants to merge 3 commits into
mainfrom
fix/issue-28-pack-visibility

Conversation

@Xe

@Xe Xe commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • A failed pack upload stays fatal for its writer. A later ref update on that writer cannot publish objects that are missing from the bucket. The storer flushes every bin and cue upload before it publishes a ref, so a hook can clone the ref safely.
  • A reader can find packs that another writer published after the reader's first listing. On a miss, the reader lists packs/*.cue again. Concurrent misses share one refresh, and a cooldown limits how often the reader lists the bucket.
  • HTTP shallow discovery ends its response at the shallow-update flush. The response no longer has an extra NAK.

Refs #28. We do not know the cause of the production failure yet, so this PR does not close the issue.

Details

Pack refresh

  • A failed refresh does not make a read fail. Loose storage already returned 404, so the read returns ErrObjectNotFound. go-git accepts only that error for an unknown have. Any other error stops the whole upload-pack. The storer logs each failed refresh once, at warn level. A canceled storer still returns the cancellation error.
  • The cooldown starts at 1 s. A refresh that finds no new pack, or that fails, doubles the cooldown, up to 10 s. A refresh that finds a pack sets the cooldown back to 1 s. This prevents many unknown haves from making the reader list the bucket again and again.
  • During a refresh, known objects stay readable. Local registrations and iterator snapshots stay the same. The refresh never loads a pack that this storer registered while the listing was in progress.
  • When an upload fails, the storer removes that pack from its index. If the bucket has the pack after all, a later refresh loads it.

Upload-pack

  • uploadpack.go contains a small fork of go-git's transport.UploadPack. When a stateless request contains a deepen and nothing follows it, the fork writes the shallow update and returns. All other requests use the upstream code path.

Receive-pack

  • The waiting for packfiles to finish pushing log line is removed. The storer already makes the uploads finish before it publishes a ref. TestTigrisPushPublication holds the fake S3 uploads open and checks that order.

Tests and docs

  • Gated S3 tests use the real AWS SDK, the Tigris storer, the daemon, and real Git HTTP and SSH clients. They cover upload failures, the order of uploads and hooks, fresh shallow clones, and later fetches.
  • Storer tests cover the adaptive cooldown, packs registered during a refresh, reloading a deregistered pack, and cancellation.
  • docs/architecture/{hooks,tigris-storer,transports}.md describe the new behavior. docs/usage/pack-publication-check.md contains the procedure to validate on a personal cluster.

Test plan

  • go build ./...
  • go vet on the changed packages
  • go test ./.... The commit hook also ran it.
  • The commit hooks formatted the changed files with goimports and Prettier
  • Validation on a live bucket and a personal cluster. The operator can use the procedure in docs/usage/pack-publication-check.md.

The bucket consistency requirements do not change.

Xe added 2 commits October 15, 2026 14:49
Retain upload failures across ref updates and log the upload barrier before hooks. Recover stale pack indexes with one coalesced refresh while preserving known-object reads.

Exercise the real S3 SDK and Tigris storer over HTTP and SSH with gated and failed uploads. End HTTP shallow-discovery responses at their flush to avoid an extra NAK. Add personal-cluster validation instructions.

Refs: #28

Assisted-by: GPT-6 via Codex
Signed-off-by: Xe Iaso <xe@tigrisdata.com>
Let long-lived readers retry pack discovery after missing probes and
storage errors. Coalesce concurrent refreshes and wait one second after
each attempt before allowing another scan. Keep known objects readable.

Forward HTTP upload request packets without rebuilding or sorting the
wants list. Release the pooled packet buffer after negotiation. Add
recovery, cancellation, framing, and allocation benchmarks.

Refs: #28
Assisted-by: GPT-6 via Codex
Signed-off-by: Xe Iaso <xe@tigrisdata.com>
@Xe
Xe requested a balanced review from Copilot October 16, 2026 03:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Concurrent storage refresh and wire-protocol changes warrant human review, with live-bucket and cluster validation still pending.

0 open findings

What changed in this PR

Hardens ObjGit’s pack publication and reader recovery for issue #28 without claiming to resolve the unconfirmed production cause.

Changes:

  • Keeps upload failures fatal and logs the publication barrier before hooks.
  • Adds coalesced pack-index refreshes and corrects HTTP shallow-response framing.
  • Expands regression coverage and documents operator validation.
File Description
internal/​storage/​tigris/​visibility_test.go Tests refresh recovery, concurrency, and cancellation.
internal/​storage/​tigris/​upload.go Preserves the first upload failure.
internal/​storage/​tigris/​tigris.go Supports configured S3 clients.
internal/​storage/​tigris/​publication_test.go Tests upload ordering and failed publication.
internal/​storage/​tigris/​packindex.go Adds coalesced refreshes with cooldowns.
internal/​storage/​tigris/​objects.go Refreshes packs after object misses.
internal/​storage/​tigris/​livebucket_test.go Verifies published objects through fresh readers.
internal/​storage/​tigris/​client_test.go Adds upload gates to the storage fixture.
docs/​usage/​pack-publication-check.md Provides cluster validation steps.
docs/​plans/​2026-10-15-issue-28-pack-visibility.md Records investigation, implementation, and validation.
docs/​architecture/​transports.md Explains HTTP shallow-response handling.
docs/​architecture/​tigris-storer.md Documents upload failures and refresh behavior.
docs/​architecture/​hooks.md Clarifies publication-before-hook ordering.
cmd/​objgitd/​uploadpack.go Adds bounded HTTP shallow-round handling.
cmd/​objgitd/​uploadpack_test.go Tests packet forwarding and response framing.
cmd/​objgitd/​ssh_test.go Checks wait-log ordering before SSH hooks.
cmd/​objgitd/​s3_publication_test.go Exercises publication and fetches through HTTP/SSH.
cmd/​objgitd/​receivepack.go Logs before the publication barrier.
cmd/​objgitd/​publication_test.go Tests publication barriers and callback behavior.
cmd/​objgitd/​http.go Routes fetches through the HTTP adapter.
cmd/​objgitd/​http_test.go Checks wait-log ordering before HTTP hooks.
cmd/​objgitd/​hooks_test.go Adds a shared log-order assertion.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

A failed pack refresh made a read return a storage error. During fetch
negotiation go-git only tolerates ErrObjectNotFound for an unknown
have, so one bad listing failed the whole upload-pack. A miss now reads
as absent when the refresh fails, and the failure is logged once per
attempt. A canceled storer still reports the cancellation.

The refresh cooldown now adapts. An attempt that finds no new pack, or
fails, doubles the next cooldown up to ten seconds. An attempt that
finds a pack resets it to one second. This stops a long list of
unknown haves from listing the bucket over and over.

A pack whose upload failed is now forgotten, so a later refresh can
load it if the bucket has it after all. A pack that this storer
registers during a refresh is never loaded twice.

The HTTP shallow-discovery fix now lives in a small fork of go-git's
UploadPack. It ends the response at the shallow-update flush when the
request body is exhausted after the deepen.

Receive-pack drops its own "waiting for packfiles" log. The storer
already flushes every pack upload before it publishes a ref, and
TestTigrisPushPublication checks that order.

Refs: #28
Assisted-by: Claude Opus 5.5 via Claude Code
Signed-off-by: Xe Iaso <xe@tigrisdata.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants