Skip to content

fix(replication): unified poison-record policy — decode failure kicks the link, never silent-skips (task #48)#315

Merged
pilotspacex-byte merged 5 commits into
mainfrom
fix/replica-poison-policy
Jul 13, 2026
Merged

fix(replication): unified poison-record policy — decode failure kicks the link, never silent-skips (task #48)#315
pilotspacex-byte merged 5 commits into
mainfrom
fix/replica-poison-policy

Conversation

@pilotspacex-byte

Copy link
Copy Markdown
Contributor

Task #48: one policy for malformed replicated records across graph/MQ/WS/temporal apply paths: decode failure = desync evidence → rate-limited loud log + new replication_poison_records_total INFO counter + connection drop so the reconnect/resync loop renegotiates PSYNC. Semantic apply errors on well-formed records keep warn-and-continue (data divergence ≠ stream corruption; line documented in module docs). Worst pre-fix case: WS create/drop swallowed poison entirely while reporting success.

6 new red→green poison tests (per plane, asserting Poisoned outcome + counter delta + no state change; found and fixed a shared-atomic test race with a dedicated lock). Full lib 4268 green on VM, fmt+clippy both matrices clean. Refs task #48, PR #292-#294 planes.

…ral apply planes

Task #48: each replica-apply plane (graph WAL replay, MQ effect records,
workspace create/drop, temporal invalidate, RESP framing) handled a
malformed/undecodable record ad hoc — mostly warn+silently-skip-and-continue,
and WS.CREATE.APPLY/WS.DROP.APPLY even swallowed the failure entirely
(try_with_shard(...).is_some() returned true regardless of the inner decode
outcome). A silent skip lets every subsequent record in the stream keep
applying against state that has already diverged from the master, invisible
until an operator notices missing data.

Define one policy and apply it everywhere: a decode/parse failure on the live
stream is protocol-level desync evidence, never silently skipped and never
panicked on. Log loud (rate-limited to 1/sec), count it in a new
INFO-visible counter, and signal the caller to drop the connection so the
existing reconnect/resync loop renegotiates PSYNC — the only real recovery
path. PSYNC snapshot install already failed the whole install on a malformed
aux blob (fail-closed); it now also feeds the same counter.

Semantic apply errors on well-formed records (WRONGTYPE, missing entity,
etc.) keep the existing warn-and-continue posture, matching the generic KV
dispatch path's long-standing behavior — that is data-level divergence from
a legitimately-executed command, not stream corruption.

- src/replication/apply.rs: new ApplyOutcome enum (Applied/Poisoned/
  NoShardSlice) returned by apply_local; poison() helper (rate-limited warn +
  REPL_POISON_RECORDS_TOTAL counter); apply_graph/apply_mq/apply_ws_create/
  apply_ws_drop/apply_temporal_invalidate now return bool instead of
  logging-and-swallowing; drain_replicated_commands' fatal RESP-parse path
  and load_snapshot's fail-closed aux-blob errors also feed the counter.
- src/replication/replica.rs: both tokio and monoio stream-apply loops match
  on ApplyOutcome and drop the connection on Poisoned, same as the existing
  NoShardSlice / DrainResult::fatal paths.
- src/command/connection.rs: INFO replication section gains
  replication_poison_records_total.
- Tests (red/green): one per plane (graph/MQ/WS-create/WS-drop/temporal)
  feeding a corrupt record through apply_local, asserting Poisoned outcome +
  counter increment (+ untouched workspace registry for the WS case), plus a
  well-formed-record control asserting the counter does NOT advance.

author: Tin Dang
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@TinDang97, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 59 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 877f8213-0e5f-44cb-9c3c-a16287598d6a

📥 Commits

Reviewing files that changed from the base of the PR and between 13ca6ec and 60b685f.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • src/command/connection.rs
  • src/replication/apply.rs
  • src/replication/replica.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/replica-poison-policy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…roof test lock

Under runtime-tokio,jemalloc (CI feature set, no graph) apply_graph and
the TEMPORAL.INVALIDATE-AT arm are compiled out, so those records fall
to generic dispatch (warn-and-continue) — the Poisoned contract only
exists when the graph plane does. Also acquire POISON_TEST_LOCK via
unwrap_or_else(into_inner) so one failing test no longer cascades
poisoned-mutex panics into the other five.

author: Tin Dang
@pilotspacex-byte pilotspacex-byte merged commit af65a29 into main Jul 13, 2026
1 check was pending
@TinDang97 TinDang97 deleted the fix/replica-poison-policy branch July 13, 2026 11:20
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