Repository navigation
fix: wait for a pending job identity instead of denying the restore - #15
Merged
Merged
Conversation
JobStarted reaches the daemon tens of seconds after the guest dequeues the job (p50 ~30 s, p90 ~47 s on two hosts over ~4,300 jobs), and a workflow's first actions/cache restore usually runs before that. Denying a pending lease at once turned about half of all restores into misses. The cache server now polls a pending lease until it resolves, turns unresolved, changes generation, the client leaves, or --identity-wait (default 60s) runs out. The registry marks a lease unresolved when resolution gives up, so readers stop waiting at once. Denials are counted by reason (missing, pending, unresolved, expired, invalid, generation) in /stats and as a label on firerunner_cache_identity_denied_total, alongside wait count and time. Co-Authored-By: Claude <noreply@anthropic.com>
Member
Author
|
babysit-pr · head |
There was a problem hiding this comment.
🟢 Approval recommended
No blocking issues were identified; bounded waiting preserves authorization and generation checks with focused regression coverage.
No open issues 🎉
What changed in this PR
Prevents early cache restores from becoming misses while job identity resolution is pending, preserving existing authorization checks.
Changes:
- Adds configurable waiting for pending identities and terminal unresolved records.
- Exposes denial reasons and wait metrics.
- Adds Go and real-toolkit regression coverage.
| File | Description |
|---|---|
| test/cache-protocol/test.mjs | Tests pending restores and unresolved denials. |
| test/cache-protocol/README.md | Documents expanded integration coverage. |
| README.md | Explains waiting, terminal failures, and metrics. |
| internal/jobidentity/registry.go | Publishes terminal resolution failures. |
| internal/jobidentity/identity.go | Defines unresolved and pending identity behavior. |
| internal/jobidentity/identity_test.go | Tests terminal failures and pending classification. |
| internal/cacheserver/scope.go | Implements bounded waiting and denial classification. |
| internal/cacheserver/scope_test.go | Tests waiting, termination paths, and metrics. |
| internal/cacheserver/cacheserver.go | Initializes waiting defaults and exports metrics. |
| cmd/firerunner/main.go | Adds the --identity-wait option. |
Review effort: Balanced
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
#14 grants cache rights only once the daemon has handled
JobStartedand resolved the job through the REST API. Until then, the VM's lease is pending, and the cache server denies every request from it immediately.JobStartedis late. On two production hosts over 24 hours (about 4,300 jobs), the scale-setJobStartedmessage reached the daemon this long after the guest's ownRunning job:console line:A typical workflow's first
actions/cacherestore runs 20–40 s into the job (checkout, then setup). So under #14 as merged, roughly half of all restores land before the lease resolves and turn into misses. Saves at the end of the job are unaffected, which hides the problem: the cache keeps filling and the hit rate quietly halves.What
A request from a pending lease waits instead of being denied. The cache server polls the record (every 250 ms) until one of these happens:
unresolved(see 2), and the request is denied at once;--identity-waitruns out (default 60 s;0keeps the old deny-at-once behaviour), and the request is denied;Waiting grants nothing new: the same
Validateand connection-generation check run on the record it ends with.@actions/http-clientallows 3 minutes per request, so a 60 s wait is inside the toolkit's own limit. The HTTP server has noWriteTimeoutto trip.Failed resolution is now terminal and visible to readers. Before this PR, a lease whose resolution gave up looked exactly like one still in flight. With waiting, every request from that job would sit out the full wait. The registry now writes
unresolved: truewhen it gives up,Validaterejects such a record, andPendingexcludes it.Denials are counted by reason.
/statskeepsidentity_denied(the total) and addsidentity_denied_by_reason,identity_waitsandidentity_wait_seconds./metrics:firerunner_cache_identity_denied_totalgains areasonlabel (missing,pending,unresolved,expired,invalid,generation), sosum()over it equals the old series.firerunner_cache_identity_waits_totalandfirerunner_cache_identity_wait_seconds_total.pendingcount means the wait is shorter than theJobStartedlag.unresolvedpoints at App permissions or the REST API.Stale-generation denials were not counted before; they now are, as
generation.Tests
unresolved, slot reuse, client gone.unresolved, andPendingandValidateboth refuse such a record.test/cache-protocol):@actions/cacherestore started 3 s before its lease resolves hits the default-branch entry;unresolvedlease misses without waiting.unresolvedas pending;unresolved;Validateacceptingunresolved;