Skip to content

PCSM-128: Checkpoint before index changes - #296

Draft
chupe wants to merge 24 commits into
PCSM-405from
PCSM-128
Draft

chupe wants to merge 24 commits into
PCSM-405from
PCSM-128

Conversation

@chupe

@chupe chupe commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Persists a drained checkpoint before creating or dropping target indexes, so recovery doesn't replay older writes against a newer index definition. Uses exact startAfter predecessors and acknowledged requests through the existing tenure writer. Finalization releases the pipeline lock while draining and retains exclusive ownership.

Stacked on #283 for checkpoint write ordering. Existing checkpoints remain readable, but this doesn't repair an already-affected target.

Verified locally: lint, race-enabled unit tests, MongoDB 7 integration tests, 207 replica-set E2E tests, and the actual insert/delete/text-index/restart sequence. The prerequisite baseline reproduces the reported error. The fixed binary preserves documents and indexes and continues replication after restart.

Go CI passes, including MongoDB 6, 7, and 8 integration jobs. The complete E2E Actions matrix passes at 3ee8e2b. Its MongoDB 8 sharded job passed on rerun after target majority-write timeouts caused lease loss and an HTTP 409 before the failed test started cloning. No assertion was changed for that rerun. Qodo's findings are resolved at 3ee8e2b; the last commit only changes test timeout assertions.

Functional CI used QA main rather than the prerequisite's QA branch and failed on obsolete interrupted-clone and unproven oplog-eviction expectations. The interrupted-clone failure also reproduces on the unchanged prerequisite. Its QA branch already corrects both expectations.

A local companion QA fixture creates the sharding index before cloning so index checkpointing does not invalidate its deliberate stale-checkpoint window. All assertions remain. Five targeted cases pass without skips: sharding replay, oplog eviction, clone interruption, and PCSM-128 recovery on both topologies. Publication of that companion QA branch awaits approval.

Draft pending functional CI and the full Jenkins suite against the matching QA revision. Jenkins #278 was intentionally aborted because it used QA main; it is not a test verdict.

sandraromanchenko and others added 20 commits October 19, 2026 18:15
… snapshots

A fence-driven demotion now records the step-down in membership (atomic
StepDown, bound to the epoch that raised it), so a lease the instance still
holds renews as a real transition back to ACTIVE and the suspended pipeline
resumes instead of sitting without checkpointing until a restart.

Checkpoint snapshots carry a sequence allocated under the pipeline lock and
never rewound by Recover. Within a term the write predicate orders on it, so
a canceled writer's late update of an older snapshot is dropped as a no-op
instead of overwriting a newer one.

Adapts the PCSM-389 tests to the single-writer design: the demotion test
drives onFenced, the ordering test is deterministic around the sequence.
A lease renewal interleaving between StepDown's unlock and its gauge update
could leave the exported role one transition behind the recorded one. SetRole
had the same gap. Also replaces a bare t.Fatal in the bootstrap collision
test with the require helper.
… fence

A fenced write while this instance still owns the lease means the lease
document was reset with recovery data in place: the lease renews at a term
the checkpoint rejects and nothing moves it forward, so the fence-driven
demotion would repeat on every renewal. onFenced now learns the fencing term
from the rejected write and raises the lease term past it through the owner
only Membership.AdvanceTerm, so the next renewal is a promotion at a term the
checkpoint accepts and the pipeline restores from that checkpoint.

Covered end to end with a synctest and a mocked driver, so no tick is waited
for, plus an owner only AdvanceTerm test against MongoDB.
The lease loop takes no server lock. A renewal at the stale term that
reconciled ACTIVE between the step-down and the term advance left membership
ACTIVE, the renewal at the advanced term then only updated the term, and
nothing promoted the instance again, so the queued stale promotion opened an
epoch without a checkpoint writer. After the repair, a membership that still
advertises ACTIVE steps down at that term so the next renewal is a real
transition. The synctest models the racing renewal.

fencedError unwraps to the sentinel instead of comparing in Is, and the
wrapcheck suppression in AdvanceTerm states its reason.
A fence-driven demotion that steps down after a renewal raced it leaves the
renewal's ACTIVE transition queued. Consuming it resumed the pipeline for an
instance advertising STANDBY until the next renewal replaced the epoch.
onPromote now checks membership's role before it launches anything; the next
renewal emits a fresh transition. The synctest consumes the stale transition
and asserts no epoch opens.
Rely on term and snapshot sequence to reject late checkpoint writes. Drop the promotion join and call onFenced directly from the checkpoint loop; keep epoch cancellation and pipeline suspension.
Check the membership term before opening an epoch or resuming the pipeline. Cover a stale promotion that reaches the server lock after renewal at the repaired term.
A renewal response at the stale term that lands after onFenced has
finished reconciles ACTIVE and queues that promotion. When the renewal
at the repaired term returns before the queue is consumed, only the
term changes; same-role renewals never emitted, so the queued promotion
was rejected as stale and the instance stayed ACTIVE at the repaired
term with no epoch and no checkpoint writer. An ACTIVE renewed at a new
term re-acquired the lease, so it is a transition now, and the promotion
at the new term replaces the stale one.

The synctest drives both renewals through the real lease tick against
the mocked driver and asserts an epoch and a checkpoint at the repaired
term; it failed before the change.
Only checkpoint writes are fenced by term. Cancellation contains a
deposed instance's data writes; a command already on the wire may still
apply, as the root AGENTS.md already says. Also record that an ACTIVE
keeps its role through renewal errors within the local lease deadline.
…ease ownership

The lease loop reports the ownership each attempt established, as a
level, to one lifecycle controller instead of emitting coalesced role
transitions. The controller owns the role, the term and the ACTIVE tenure:
every tenure builds a fresh pipeline and restores it from the checkpoint,
and any loss of ownership or a fenced checkpoint write ends the tenure as a
whole, canceling, joining and discarding its pipeline. No two tenures of a
process write under one term: a tenure opens only at or above minTerm, and
ownership reported below it is raised through the owner-only AdvanceTerm.

Removes the same-term in-memory resume (Suspend's settle semantics,
suspendedOnDemote, activeTerm, promotionPlan), the RoleChange channel,
StepDown and the stale-promotion guards, and the in-memory clone resume
with its cross-attempt inventory. Restore refuses a checkpoint owned by a
newer term before recovering anything, and requests are admitted once the
tenure's restore has finished.
…re the join

A request that reaches a tenure still restoring its pipeline now waits for
the restore, within its own context, instead of a 409: the HA harness picks
an ACTIVE by its advertised role and issues a strict /start right away. The
deferred --start is applied before requests are admitted, so a /start cannot
pre-empt the configured options.

Ending a tenure advertises STANDBY before joining its work, and the join
honors the caller's context; the startup Status call runs on the tenure's
context, so cancellation reaches its source request. A report that ended a
tenure opens nothing itself: the next report, a fresh level, does. A clone
ended by cancellation logs as canceled, not failed. The test helper that
awaits a tenure no longer races its own readiness against the opener's
return, and comments that still described the previous lifecycle are
corrected.
A request that expired while waiting for the tenure's restore was still
admitted when the restore finished first, and its lifecycle operation could
launch for a caller that had gone away. The request's context is checked
after the wait, whatever the tenure's state by then.
Claim the checkpoint before restoring it and stop the tenure after a checkpoint error. Document recovery invariants and diagnostic metadata, and cover retry, timeout and lost-response behavior.
@chupe

chupe commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

/agentic_review

@qodo-code-review

qodo-code-review Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


🔴 High

1. Finalize fails and stalls replication when draining takes over 5s ✓ Resolved
Description
Finalize now waits on ctx.Done() during the unlocked drain and returns `wait for change
replication once the request context ends. It has already called rpl.Pause`, so the repl run still
stops. HandleFinalize gives that context a 5-second ServerResponseTimeout. Any drain longer than
that fails the request: a backlog of queued events, slow bulk writes, or the new acknowledged
checkpoint before an index DDL can each cause it. The pipeline is then left in StateRunning with
change replication stopped and no finalizer started. PCSM.run exits with no error, so the state
never changes, and /pause and /resume both refuse it. Before this PR, Finalize blocked on
<-p.repl.Done() regardless of the request context.
Code

pcsm/pcsm.go[R1098-1100]

+	if ctx.Err() != nil {
+		return errors.Wrap(ctx.Err(), "wait for change replication")
+	}
Relevance

●●● Strong

Context cancellation can strand replication in running state; recent Finalize history accepted
context-aware drain fixes.

PR-#280

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The HTTP handler limits Finalize to 5s. Finalize pauses repl, then gives up on ctx expiry without
moving the PCSM state forward. Pause is non-blocking and the run ends for good once paused. PCSM.run
calls setFailed only when repl reports an error, so the state stays 'running' with nothing
replicating. doPause refuses because repl is not running, and Resume refuses because the state is
not Paused.

main.go[1221-1246]
pcsm/pcsm.go[1071-1100]
pcsm/pcsm.go[750-762]
pcsm/pcsm.go[950-970]
pcsm/repl/repl.go[443-484]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Once Finalize has paused change replication, it returns an error if the HTTP request context (5s timeout) expires during the drain. The pipeline is left in StateRunning with replication stopped and no finalizer started.

## Fix Focus Areas
- pcsm/pcsm.go[1071-1121]
- main.go[1221-1250]

## Recommended Fix
After repl.Pause succeeds, stop tying completion to the request ctx. Wait only on `done` and `parent.Done()` (the epoch), or move the drain plus startFinalize into a goroutine owned by the reservation (finalizeActive), so the HTTP call returns after the pause has been accepted. If the request context must still be honoured, move the pipeline to an observable state on that path, for example StatePaused, so the operator can resume or finalize. Add a test where the drain takes longer than the request context.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


🟠 Medium

2. A checkpoint test skips shared assertions ✓ Resolved
Description
TestIndexCheckpoint_DrainBeforeAcknowledgementBeforeIndex calls t.Fatal directly when the
checkpoint is not requested. This timeout branch is a behavior assertion in a changed test and
bypasses the repository's required assert/require helpers.
Code

pcsm/repl/index_checkpoint_test.go[R159-161]

+				case cp = <-requested:
+				case <-ctx.Done():
+					t.Fatal("checkpoint was not requested after drain")
Relevance

●●● Strong

Recent accepted precedents require testify helpers for direct timeout assertion failures in Go
tests.

PR-#277
PR-#282

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The changed test directly invokes t.Fatal in a logical assertion branch, while the checklist
requires testify assertion helpers for value and condition checks.

Rule 2904344: Use testify assert/require for assertions in Go test files
pcsm/repl/index_checkpoint_test.go[159-161]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

Issue description
The changed checkpoint test uses `t.Fatal` directly for a logical behavior assertion when the checkpoint is not requested.

Fix Focus Areas
- pcsm/repl/index_checkpoint_test.go[159-161]

Recommended Fix
Replace the direct `t.Fatal` call with an appropriate testify `require` or `assert` helper while preserving the timeout diagnostic message and test failure behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

3. Filtered invalidate reconnects without its startAfter token ✓ Resolved
Description
watchWithRetry no longer uses invalidateErr.invalidEventID for sharded sources. The reconnect
position now comes only from the dispatcher's invalidate variable, which is set only in the `case
Invalidate branch. Invalidate events have no namespace, so with an include filter nsFilter("",
"")` returns false and the dispatcher skips them earlier, at the filter check. The reconnect then
falls back to SetStartAtOperationTime(checkpointOpTime), which is at or before the invalidate.
That reopens the stream onto the same invalidate, where the old code resumed after it with
startAfter.
Code

pcsm/repl/repl.go[R896-900]

+			opts := r.resumeOptions()
+			predecessor = slices.Clone(r.checkpointStartAfter)
+			if invalidate != nil && !r.checkpointOpTime.After(invalidate.ClusterTime) {
+				predecessor = slices.Clone(invalidate.ID)
+				opts = options.ChangeStream().SetStartAfter(predecessor)
Relevance

●●● Strong

Filtered invalidate reconnect can replay the invalidate; accepted history prioritizes preserving
exact invalidate startAfter tokens.

PR-#222
PR-#277

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The dispatcher runs the nsFilter check before the switch and continues, so invalidate = change
is never reached for a filtered event. MakeFilter denies any db not in the include list, including
"". The removed watchWithRetry code set startAfter from the invalidate error for sharded sources no
matter what the dispatcher did. advanceCheckpoint can raise the floor only up to the invalidate's
own timestamp, and SetStartAtOperationTime is inclusive.

pcsm/repl/repl.go[946-972]
pcsm/repl/repl.go[894-911]
sel/sel.go[17-58]
pcsm/repl/repl.go[566-592]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The dispatcher records `invalidate` only after the PCSM-database and namespace filter checks. With an include filter, a namespace-less invalidate event is filtered out, and the reconnect uses an inclusive timestamp instead of startAfter on the invalidate token.

## Fix Focus Areas
- pcsm/repl/repl.go[894-990]
- pcsm/repl/repl.go[566-592]

## Recommended Fix
Handle `change.OperationType == Invalidate` before the PCSMDatabase and nsFilter checks, or at least set `invalidate = change` for any invalidate the dispatcher accounts for. The reconnect fence should then always use startAfter(invalidate.ID) while the floor has not passed it. Add a unit test that runs an include filter with a sharded invalidate followed by a reconnect fence.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

4. Progress tests bypass shared timeout policy ✓ Resolved
Description
TestIndexCheckpoint_DispatchPredecessors, TestIndexCheckpoint_ReconnectAfterInvalidate, and
TestIndexCheckpoint_DrainBeforeAcknowledgementBeforeIndex create deadlines with
context.WithTimeout instead of util.CtxWithTimeout. When these replication tests set up their
timeout contexts, all three bypass the repository helper even though none tests the timeout utility
itself.
Code

pcsm/repl/repl_progress_test.go[213]

+			ctx, cancel := context.WithTimeout(t.Context(), barrierTimeout)
Relevance

●●● Strong

Repository tests have accepted fixes replacing direct timeout construction with the shared timeout
helper.

PR-#277

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The cited timeout setup sites in all three tests call context.WithTimeout directly. Because these
are replication tests rather than tests of the timeout utility, those calls establish that the tests
bypass the required util.CtxWithTimeout path.

Rule 2904342: Use util.CtxWithTimeout for all context timeouts
pcsm/repl/repl_progress_test.go[205-214]
pcsm/repl/repl_progress_test.go[283-290]
pcsm/repl/index_checkpoint_test.go[98-105]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Three replication tests create timeout contexts directly with `context.WithTimeout` instead of using the repository timeout helper.

## Fix Focus Areas

## Recommended Fix
In `TestIndexCheckpoint_DispatchPredecessors`, `TestIndexCheckpoint_ReconnectAfterInvalidate`, and `TestIndexCheckpoint_DrainBeforeAcknowledgementBeforeIndex`, replace the direct timeout-context creation with `util.CtxWithTimeout`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Context sources
✅ Linked PRs:
  🌐 PR #283
  🌐 PR #278
✅ Compliance rules (platform): 21 rules
✅ Cross-repo context — repo relationships
  Explored: repo: percona/percona-server-mongodb (sha: 56a03457) — View relationship
  Explored: repo: Percona-QA/psmdb-testing (sha: 4f501053) — View relationship
Review mode: Auto: 🚀 Fast: Focused changes only add localized test timeout assertions.

Tip of the day
💡 Did you know, you can tweak Display settings with a live preview to see your comment before it ships

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗


Previous reviews

Review updated until commit 3ee8e2b 🚀 Fast

Results up to commit 7954cca 🧠 Deep


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


🔴 High

1. Finalize fails and stalls replication when draining takes over 5s ✓ Resolved
Description
Finalize now waits on ctx.Done() during the unlocked drain and returns `wait for change
replication once the request context ends. It has already called rpl.Pause`, so the repl run still
stops. HandleFinalize gives that context a 5-second ServerResponseTimeout. Any drain longer than
that fails the request: a backlog of queued events, slow bulk writes, or the new acknowledged
checkpoint before an index DDL can each cause it. The pipeline is then left in StateRunning with
change replication stopped and no finalizer started. PCSM.run exits with no error, so the state
never changes, and /pause and /resume both refuse it. Before this PR, Finalize blocked on
<-p.repl.Done() regardless of the request context.
Code

pcsm/pcsm.go[R1098-1100]

+	if ctx.Err() != nil {
+		return errors.Wrap(ctx.Err(), "wait for change replication")
+	}
Relevance

●●● Strong

Context cancellation can strand replication in running state; recent Finalize history accepted
context-aware drain fixes.

PR-#280

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The HTTP handler limits Finalize to 5s. Finalize pauses repl, then gives up on ctx expiry without
moving the PCSM state forward. Pause is non-blocking and the run ends for good once paused. PCSM.run
calls setFailed only when repl reports an error, so the state stays 'running' with nothing
replicating. doPause refuses because repl is not running, and Resume refuses because the state is
not Paused.

main.go[1221-1246]
pcsm/pcsm.go[1071-1100]
pcsm/pcsm.go[750-762]
pcsm/pcsm.go[950-970]
pcsm/repl/repl.go[443-484]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Once Finalize has paused change replication, it returns an error if the HTTP request context (5s timeout) expires during the drain. The pipeline is left in StateRunning with replication stopped and no finalizer started.

## Fix Focus Areas
- pcsm/pcsm.go[1071-1121]
- main.go[1221-1250]

## Recommended Fix
After repl.Pause succeeds, stop tying completion to the request ctx. Wait only on `done` and `parent.Done()` (the epoch), or move the drain plus startFinalize into a goroutine owned by the reservation (finalizeActive), so the HTTP call returns after the pause has been accepted. If the request context must still be honoured, move the pipeline to an observable state on that path, for example StatePaused, so the operator can resume or finalize. Add a test where the drain takes longer than the request context.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


🟠 Medium

2. Filtered invalidate reconnects without its startAfter token ✓ Resolved
Description
watchWithRetry no longer uses invalidateErr.invalidEventID for sharded sources. The reconnect
position now comes only from the dispatcher's invalidate variable, which is set only in the `case
Invalidate branch. Invalidate events have no namespace, so with an include filter nsFilter("",
"")` returns false and the dispatcher skips them earlier, at the filter check. The reconnect then
falls back to SetStartAtOperationTime(checkpointOpTime), which is at or before the invalidate.
That reopens the stream onto the same invalidate, where the old code resumed after it with
startAfter.
Code

pcsm/repl/repl.go[R896-900]

+			opts := r.resumeOptions()
+			predecessor = slices.Clone(r.checkpointStartAfter)
+			if invalidate != nil && !r.checkpointOpTime.After(invalidate.ClusterTime) {
+				predecessor = slices.Clone(invalidate.ID)
+				opts = options.ChangeStream().SetStartAfter(predecessor)
Relevance

●●● Strong

Filtered invalidate reconnect can replay the invalidate; accepted history prioritizes preserving
exact invalidate startAfter tokens.

PR-#222
PR-#277

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The dispatcher runs the nsFilter check before the switch and continues, so invalidate = change
is never reached for a filtered event. MakeFilter denies any db not in the include list, including
"". The removed watchWithRetry code set startAfter from the invalidate error for sharded sources no
matter what the dispatcher did. advanceCheckpoint can raise the floor only up to the invalidate's
own timestamp, and SetStartAtOperationTime is inclusive.

pcsm/repl/repl.go[946-972]
pcsm/repl/repl.go[894-911]
sel/sel.go[17-58]
pcsm/repl/repl.go[566-592]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The dispatcher records `invalidate` only after the PCSM-database and namespace filter checks. With an include filter, a namespace-less invalidate event is filtered out, and the reconnect uses an inclusive timestamp instead of startAfter on the invalidate token.

## Fix Focus Areas
- pcsm/repl/repl.go[894-990]
- pcsm/repl/repl.go[566-592]

## Recommended Fix
Handle `change.OperationType == Invalidate` before the PCSMDatabase and nsFilter checks, or at least set `invalidate = change` for any invalidate the dispatcher accounts for. The reconnect fence should then always use startAfter(invalidate.ID) while the floor has not passed it. Add a unit test that runs an include filter with a sharded invalidate followed by a reconnect fence.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

3. Progress tests bypass shared timeout policy ✓ Resolved
Description
TestIndexCheckpoint_DispatchPredecessors, TestIndexCheckpoint_ReconnectAfterInvalidate, and
TestIndexCheckpoint_DrainBeforeAcknowledgementBeforeIndex create deadlines with
context.WithTimeout instead of util.CtxWithTimeout. When these replication tests set up their
timeout contexts, all three bypass the repository helper even though none tests the timeout utility
itself.
Code

pcsm/repl/repl_progress_test.go[213]

+			ctx, cancel := context.WithTimeout(t.Context(), barrierTimeout)
Relevance

●●● Strong

Repository tests have accepted fixes replacing direct timeout construction with the shared timeout
helper.

PR-#277

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The cited timeout setup sites in all three tests call context.WithTimeout directly. Because these
are replication tests rather than tests of the timeout utility, those calls establish that the tests
bypass the required util.CtxWithTimeout path.

Rule 2904342: Use util.CtxWithTimeout for all context timeouts
pcsm/repl/repl_progress_test.go[205-214]
pcsm/repl/repl_progress_test.go[283-290]
pcsm/repl/index_checkpoint_test.go[98-105]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Three replication tests create timeout contexts directly with `context.WithTimeout` instead of using the repository timeout helper.

## Fix Focus Areas

## Recommended Fix
In `TestIndexCheckpoint_DispatchPredecessors`, `TestIndexCheckpoint_ReconnectAfterInvalidate`, and `TestIndexCheckpoint_DrainBeforeAcknowledgementBeforeIndex`, replace the direct timeout-context creation with `util.CtxWithTimeout`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Results up to commit fdcf430 ⚖️ Balanced


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)


🟠 Medium

1. A checkpoint test skips shared assertions ✓ Resolved
Description
TestIndexCheckpoint_DrainBeforeAcknowledgementBeforeIndex calls t.Fatal directly when the
checkpoint is not requested. This timeout branch is a behavior assertion in a changed test and
bypasses the repository's required assert/require helpers.
Code

pcsm/repl/index_checkpoint_test.go[R159-161]

+				case cp = <-requested:
+				case <-ctx.Done():
+					t.Fatal("checkpoint was not requested after drain")
Relevance

●●● Strong

Recent accepted precedents require testify helpers for direct timeout assertion failures in Go
tests.

PR-#277
PR-#282

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The changed test directly invokes t.Fatal in a logical assertion branch, while the checklist
requires testify assertion helpers for value and condition checks.

Rule 2904344: Use testify assert/require for assertions in Go test files
pcsm/repl/index_checkpoint_test.go[159-161]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

Issue description
The changed checkpoint test uses `t.Fatal` directly for a logical behavior assertion when the checkpoint is not requested.

Fix Focus Areas
- pcsm/repl/index_checkpoint_test.go[159-161]

Recommended Fix
Replace the direct `t.Fatal` call with an appropriate testify `require` or `assert` helper while preserving the timeout diagnostic message and test failure behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Results up to commit 3ee8e2b ⚖️ Balanced


No changes from previous review


Powered by Qodo

Comment thread pcsm/repl/repl_progress_test.go Resolved
Comment thread pcsm/pcsm.go Resolved
Comment thread pcsm/repl/repl.go Resolved
Comment thread pcsm/repl/AGENTS.md
- Events for one document route to the same worker by document-key hash to preserve order.
- Each worker builds bulks in its main loop and executes sealed bulks in a separate writer goroutine.
- `lastReplicatedOpTime` is the reported frontier; `checkpointOpTime` is the inclusive resume floor, not proof the event at that timestamp was applied. Replay detection keeps strict `<` and applies on every topology: `watchWithRetry` reopens inclusively from the checkpoint on replica sets too, and a redelivered DDL behind newer committed writes must be dropped, not re-applied.
- `lastReplicatedOpTime` is the reported frontier; `checkpointOpTime` is the inclusive resume floor, not proof the event at that timestamp was applied. An optional checkpoint `startAfter` token identifies the accounted predecessor of a protected index event. Resume/reconnect uses that exclusive token when present, otherwise the inclusive timestamp. Replay detection remains strict `<` on every topology.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ [alex] reported by reviewdog 🐶
Be careful with remains, it’s profane in some cases remains retext-profanities

}()

_, err = sourceColl.InsertOne(ctx, bson.D{
{Key: "_id", Value: "deleted"}, {Key: "a", Value: bson.D{{Key: "b", Value: bson.A{}}}},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[gofmt] reported by reviewdog 🐶

Suggested change
{Key: "_id", Value: "deleted"}, {Key: "a", Value: bson.D{{Key: "b", Value: bson.A{}}}},
{Key: "_id", Value: "deleted"},
{Key: "a", Value: bson.D{{Key: "b", Value: bson.A{}}}},

awaitSignal(ctx, t, resumedDone, "recovered run did not stop")
}()
_, err = sourceColl.InsertOne(ctx, bson.D{
{Key: "_id", Value: "after-recovery"}, {Key: "a", Value: bson.D{{Key: "b", Value: 1}}},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[gofmt] reported by reviewdog 🐶

Suggested change
{Key: "_id", Value: "after-recovery"}, {Key: "a", Value: bson.D{{Key: "b", Value: 1}}},
{Key: "_id", Value: "after-recovery"},
{Key: "a", Value: bson.D{{Key: "b", Value: 1}}},

@chupe

chupe commented Nov 2, 2026

Copy link
Copy Markdown
Collaborator Author

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit fdcf430

@chupe

chupe commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator Author

/agentic_review

Comment thread pcsm/repl/index_checkpoint_test.go Resolved
@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit fdcf430

@chupe

chupe commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator Author

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 3ee8e2b

@chupe

chupe commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator Author

/agentic_review

@qodo-code-review

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 3ee8e2b

Base automatically changed from PCSM-389 to main November 3, 2026 06:36
@chupe
chupe changed the base branch from main to PCSM-405 November 3, 2026 06:48
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