-
Notifications
You must be signed in to change notification settings - Fork 410
fix(cache): keep an aborted invalidation bump queued #1748
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
74d0e3a
fix(cache): restore a pending bump whose write is cancelled or raises
Soju06 2bf3e44
docs(openspec): scope the change to the aborted-write fix
Soju06 13d3e95
docs(openspec): state the ambiguous-abort preference
Soju06 d953c56
docs(openspec): qualify the no-version assertion for the unambiguous …
Soju06 79c47e8
docs(openspec): scope both abort branches by commit acceptance
Soju06 16a4cd6
test(cache): prove the poller retries an aborted bump
Soju06 48e32f2
docs(openspec): finish removing rationale from the requirement
Soju06 4095d19
test(cache): drop the false shutdown-delivery rationale from the docs…
Soju06 4249001
fix(cache): keep flushing other namespaces after an abnormal raise
Soju06 b167702
docs(openspec): sync the proposal with the split abort handling
Soju06 abfea30
test(cache): wait for the retry to settle before stopping the poller
Soju06 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
27 changes: 27 additions & 0 deletions
27
openspec/changes/keep-aborted-invalidation-bumps-pending/proposal.md
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| ## Why | ||
|
|
||
| The invalidation-bus spec already requires that "coalesced (`request_bump`) namespaces MUST remain pending and be retried on subsequent poll cycles until a bump succeeds". The implementation violated it for one case. | ||
|
|
||
| `_flush_pending_bumps` clears each namespace's pending marker before awaiting its write — deliberately, so a `request_bump()` arriving mid-write re-queues instead of being coalesced into the version already being written. But it restored the marker only when `bump()` returned `False`. A write that was **cancelled** or **raised** left the namespace neither written nor pending, with nothing logged and no retry holding it. Since `_run` swallows poll exceptions and keeps cycling, a raising write silently lost its namespace during ordinary operation. | ||
|
|
||
| ## What Changes | ||
|
|
||
| - Restore the pending marker when the bump write aborts, so the required retry actually happens. The two abort kinds are handled differently: `CancelledError` restores and re-raises (task teardown must abort the flush), while an ordinary `Exception` — abnormal, since `bump()` reports failure by returning `False` — restores, logs at warning, and continues, so a persistently raising namespace cannot starve the namespaces sorting after it. | ||
|
|
||
| The restore is unconditional even when the abort's outcome is ambiguous (cancellation or a driver error arriving after the database accepted the commit): a redundant bump only re-runs peers' idempotent invalidation callbacks, while dropping an unconfirmed write leaves them stale until the fallback TTL. The bus already tolerates extra version increments — `request_bump` arriving mid-flush deliberately produces one. | ||
|
|
||
| Process shutdown is deliberately out of scope: `stop()` cancels the polling task, so a bump queued at that moment has no cycle left to drain it. That is already the documented contract — "a lost bump still converges within the fallback TTL" — and guaranteeing delivery against an unresponsive database at shutdown is a separate concern with its own bounding and task-ownership design. | ||
|
|
||
| ## Why the ambiguous case still restores | ||
|
|
||
| A cancellation or driver error can arrive after the database accepted the commit, so the restore can produce a redundant bump. That is the deliberate trade: a redundant bump only re-runs peers' idempotent invalidation callbacks, while dropping an unconfirmed write leaves them stale until the fallback TTL. The bus already tolerates extra increments — a `request_bump` arriving mid-flush produces one by design. | ||
|
|
||
| ## Capabilities | ||
|
|
||
| ### New Capabilities | ||
|
|
||
| None. | ||
|
|
||
| ### Modified Capabilities | ||
|
|
||
| - `query-caching`: state explicitly that an aborted (not merely failed) write keeps its namespace queued. |
57 changes: 57 additions & 0 deletions
57
...pec/changes/keep-aborted-invalidation-bumps-pending/specs/query-caching/spec.md
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| ## MODIFIED Requirements | ||
|
|
||
| ### Requirement: Cache invalidation bumps and polling are resilient and observable | ||
| `bump()` MUST retry transient write failures (including SQLite "database is locked") with a short backoff; on final failure it MUST log at ERROR with the namespace, increment `codex_lb_cache_invalidation_bump_failures_total{namespace}`, and MUST NOT fail the originating mutation. Coalesced (`request_bump`) namespaces MUST remain pending and be retried on subsequent poll cycles until a bump succeeds, including when the write aborts rather than merely failing: an aborted write MUST restore the pending marker regardless of whether the database had already accepted its commit. A write that raises MUST NOT prevent the remaining pending namespaces from flushing in the same cycle. A `request_bump` arriving while a flush for the same namespace is already awaiting its bump MUST be preserved and produce a later bump. When any invalidation callback for a namespace fails, the poller MUST NOT acknowledge the observed version and MUST re-run that namespace's callbacks on subsequent poll cycles until they succeed. The poller MUST escalate consecutive poll failures above debug level after a bounded count (WARNING after 3, ERROR after 10) and increment `codex_lb_cache_invalidation_poll_failures_total`. | ||
|
|
||
| #### Scenario: Bump failure under database lock is observable and does not fail the mutation | ||
|
|
||
| - **GIVEN** the database rejects cache-invalidation writes with a lock error for longer than the retry budget | ||
| - **WHEN** a mutation attempts a durable namespace bump | ||
| - **THEN** the mutation itself still succeeds | ||
| - **AND** an ERROR log naming the namespace is emitted and the bump-failure counter increments | ||
|
|
||
| #### Scenario: Pending coalesced namespace flushes on the next successful cycle | ||
|
|
||
| - **GIVEN** a coalesced `request_bump` namespace failed to flush during a poll cycle | ||
| - **WHEN** the database becomes writable again | ||
| - **THEN** the next poll cycle flushes the pending namespace and increments its version | ||
|
|
||
| #### Scenario: Bump requested during an in-flight flush produces a later bump | ||
|
|
||
| - **GIVEN** a coalesced flush is awaiting the bump write for a namespace | ||
| - **WHEN** another mutation commits and requests a bump for the same namespace before the flush completes | ||
| - **THEN** the namespace is re-queued and flushed again on a subsequent cycle, incrementing the version beyond the in-flight bump | ||
|
|
||
| #### Scenario: Failed invalidation callback keeps the version unacknowledged and is retried | ||
|
|
||
| - **GIVEN** a replica observes an `account_routing` version bump | ||
| - **AND** its routing snapshot refresh fails with a transient database error | ||
| - **WHEN** the poll cycle completes | ||
| - **THEN** the replica does not record the new version as seen | ||
| - **AND** the refresh is retried on subsequent poll cycles until it succeeds | ||
|
|
||
| #### Scenario: Consecutive poll failures escalate above debug | ||
|
|
||
| - **GIVEN** a replica's poller cannot read the `cache_invalidation` table | ||
| - **WHEN** three consecutive polls fail | ||
| - **THEN** a WARNING is logged and the poll-failure counter increments | ||
|
|
||
| #### Scenario: An aborted bump write keeps its namespace queued | ||
|
|
||
| - **GIVEN** a coalesced flush has cleared a namespace's pending marker and is awaiting its bump write | ||
| - **WHEN** that write aborts — cancelled or raised — before the database accepts its commit | ||
| - **THEN** the namespace is restored to the pending set for a later cycle, and no version is written | ||
|
Soju06 marked this conversation as resolved.
Soju06 marked this conversation as resolved.
|
||
|
|
||
| #### Scenario: A raising namespace does not starve the others | ||
|
|
||
| - **GIVEN** two pending namespaces where the first (in sort order) raises on every bump attempt | ||
| - **WHEN** a flush cycle runs | ||
| - **THEN** the raising namespace stays pending with no version written | ||
| - **AND** the other namespace is bumped in that same cycle | ||
|
|
||
| #### Scenario: An abort after the commit was accepted still restores the namespace | ||
|
|
||
| - **GIVEN** a bump write aborts — cancelled, or the driver raises — after the database accepted its commit but before completion is reported | ||
| - **WHEN** the abort is handled | ||
| - **THEN** the namespace is restored to the pending set and bumped on a later cycle | ||
| - **AND** the resulting duplicate version increment is accepted | ||
13 changes: 13 additions & 0 deletions
13
openspec/changes/keep-aborted-invalidation-bumps-pending/tasks.md
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| ## 1. Fix | ||
|
|
||
| - [x] 1.1 Restore the pending namespace in `_flush_pending_bumps` when the bump write aborts: cancellation restores and re-raises; an abnormal raise restores, logs, and continues with the remaining namespaces so a persistently raising namespace cannot starve the ones sorting after it | ||
|
|
||
| ## 2. Tests | ||
|
|
||
| - [x] 2.1 A cancelled write restores the marker, and the marker is cleared before the write (locking in the intended coalescing) | ||
| - [x] 2.2 A raising write restores the marker and does not block later namespaces from flushing | ||
| - [x] 2.3 End-to-end: the running poller retries the aborted namespace and writes its version | ||
|
|
||
| ## 3. Spec | ||
|
|
||
| - [x] 3.1 Make "remains pending" explicitly cover an aborted write, not only a failed one, keeping the requirement normative and the rationale in the proposal |
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
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.