Skip to content

Restore memory context in checkpoint retry catch - #70

Open
matt-welch wants to merge 1 commit into
mainfrom
fix-is217-checkpoint-sigsegv
Open

matt-welch wants to merge 1 commit into
mainfrom
fix-is217-checkpoint-sigsegv

Conversation

@matt-welch

Copy link
Copy Markdown
Contributor

Description

The vamana worker's per-index checkpoint retry absorbs errors so one bad index can't take the whole worker down, but its catch block never restored the caller's memory context. On most failures that doesn't matter, because the real transaction abort restores the context as a side effect. But a checkpoint error raised before its own transaction has started leaves nothing open to abort, so AbortCurrentTransaction() is a no-op and the worker is left running in ErrorContext. The next time anything in the worker calls FlushErrorState(), that call resets ErrorContext and frees whatever live data happened to be allocated there, including the list of cached index OIDs the checkpoint sweep was still iterating over. On an assert build this produces a segfault from reading freed, clobbered memory; on a release build there's no crash, but the worker keeps running with its loop-scoped allocations silently exposed to the next reset of ErrorContext.

The fix restores the caller's saved memory context explicitly in the catch, after the (now conditional) transaction abort, matching the pattern already used at the other worker catch sites. It also adds an assertion at the top of the worker's heartbeat loop that catches this bug class as soon as it happens, as a clean assertion failure instead of a crash on some later, unrelated error. The existing regression test for this code path was updated to hold the injected failure across two consecutive checkpoint cycles (one cycle only leaves the context poisoned; a second cycle is what actually frees live data), and now checks that the same worker process survives with no signal termination, and that the worker's ErrorContext usage does not grow once checkpoints resume succeeding.

Real checkpoint failures that occur today (disk errors, lock conflicts, commit-time errors) all happen after the transaction has started, so the existing abort already restores the context correctly in those cases and this bug has not been observed in pr
oduction use. It was found through code inspection and reproduced with error injection. The fix is defensive: it closes the gap for any future change that does work ahead of the transaction start (a precheck, a GUC read, an added log call), which would otherwise make this reachable without needing error injection.

Related Issues

None.

Type of Change

  • Bug fix
  • New feature
  • Refactor / code cleanup
  • Documentation update
  • Test addition or update
  • Build / CI change

Pre-Merge Checklist

Build

  • make completes without errors or warnings
  • make install completes successfully

Tests

  • If this PR introduces no new behavior: existing regression tests (make installcheck) and TAP tests (test/t/) pass with no failures
  • If this PR introduces new behavior: test cases covering it were added to test/sql/ and/or test/t/
  • If this PR adds a standalone unit-test module under test/modules/: it builds and passes (make -C test/modules/<module> installcheck)

Documentation

  • Relevant docs under docs/ updated if architecture or usage changed

Testing Notes

Verified on two separate builds: one with --enable-cassert --enable-debug --enable-injection-points, one without cassert (both with --enable-injection-points, required for the test's error injection).

Confirmed the bug and the test's ability to catch it before applying the fix: on the assert build, the unmodified code crashed with SIGSEGV (signal 11) on all 3 runs attempted, each time after exactly two consecutive injected checkpoint failures logged a "will retry" line. On the release build, the unmodified code did not crash, but the test's ErrorContext growth check failed, showing usage growing from 888 to 1968 bytes over about 4 seconds with no error in flight.

After applying the fix, test/t/36_pg_catch_return_value_integrity.pl passed 3 out of 3 runs on the assert build and passed on the release build (confirmed not skipped). Temporarily reverting only the memory-context restore, while keeping the new heartbeat-loop assertion, reproduced a clean assertion failure (TRAP: failed Assert("CurrentMemoryContext != ErrorContext")) in place of the earlier intermittent crash, confirming the assertion catches this bug class as intended; the fix was then restored.

The full TAP suite and SQL regression suite were run against the fixed code. All regression test assertions passed. Two TAP files unrelated to this change (covering standby replay) reported a harness-level "no plan found" failure due to a pre-existing, unrelated assertion failure during standby replication slot cleanup; every individual test assertion in those files passed, and the failure reproduces identically whether run as part of the full suite or in isolation. This is a separate, pre-existing issue unaffected by this change and is not addressed by this PR.

- A checkpoint error raised before its transaction started left the
  worker in ErrorContext, since AbortCurrentTransaction() is a no-op
  when nothing is open to abort. The next FlushErrorState() anywhere in
  the worker then reset that context and freed data a checkpoint sweep
  was still iterating, crashing assert builds with SIGSEGV and leaving
  release builds leaking into ErrorContext.
- Restore the saved context explicitly in the catch, matching the other
  worker catch sites, and add an Assert at the top of the heartbeat loop
  so the whole bug class trips a clean TRAP instead of an intermittent
  crash.
- Make the existing regression test hold the injected failure for two
  cycles and check worker PID, signal termination, and ErrorContext
  growth, since one failure only poisons the context and a second is
  needed to free live data.

Signed-off-by: Matt Welch <matt.welch@intel.com>
@matt-welch
matt-welch requested a review from a team October 8, 2026 21:10
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.

1 participant