xlogrecovery: drain prefetch pins after the redo loop (fixes DROP TABLE spinning forever after crash recovery) - #94
Conversation
The WAL prefetcher issues io_uring reads whose buffer pins belong to the startup thread's ring slots and are only dropped when that thread collects them (bufmgr::uring::start_read calls collect_done on the next issue). After the last redo record no further prefetch is issued, so the pin on the last prefetched page was never collected. Any later DropRelationsAllBuffers on that relation (DROP TABLE, TRUNCATE, DROP DATABASE) then spins forever at 100% CPU in InvalidateBuffer: the shared refcount is non-zero, the private refcount is zero, and no IO is in progress, so WaitIO returns immediately and the loop retries. The DROP has already committed at that point and pg_cancel_backend has no effect. Call bufmgr::uring_drain_pins() once the redo loop ends, matching the collect/drain discipline used by AtEOXact_Buffers and pool-worker exit. Repro: insert into a fresh table, kill -9 without a checkpoint, restart, DROP TABLE. Verified with pg_buffercache that no buffer of the relation stays pinned after recovery and that DROP completes in milliseconds. Fixes malisper#93 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review. 📝 WalkthroughWalkthroughWAL recovery now drains pending io_uring reads after replay completes. It releases thread-owned buffer pins before recovery-target handling and subsequent cleanup. ChangesWAL recovery cleanup
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to The change drains recovery-time prefetch pins after WAL replay so recovered relations can be invalidated normally; no actionable merge-blocking risk remains after normal checks and review. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Linked Issues checkExplanation The change drains io_uring-owned prefetch pins after the redo loop, directly addressing issue
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Fixes #93.
Problem
After a crash (
kill -9) and successful WAL recovery,DROP TABLEon a relation whose pages were replayed by redo never returns: the transaction commits, then the backend thread spins at 100% CPU forever inbufmgr::drop_buffers::InvalidateBuffer, andpg_cancel_backendhas no effect. Full analysis, stack traces andpg_buffercacheevidence are in #93.Root cause
The WAL prefetcher issues io_uring reads through
PrefetchSharedBuffer -> uring::start_read.start_readpins the victim buffer and hands the pin to the issuing thread's ring slot (ForgetBufferPin); the pin is only dropped when that same thread runscollect_done(called at the start of the nextstart_read) ordrain_own.Redo issues no further prefetch after the last record, and nothing on the startup thread drains its ring at the end of recovery, so the pin on the last prefetched page is stranded.
pg_buffercacheshows exactly one buffer of the relation (its last block) withpinning_backends = 1on an idle server after recovery.InvalidateBufferthen sees shared refcount != 0, private refcount == 0 and no IO in progress, soWaitIOreturns immediately and thecontinueloop never terminates.Fix
Call
bufmgr::uring_drain_pins()once the redo loop inperform_wal_recovery_gutsends. This is the same collect/drain discipline already used byAtEOXact_Buffers,LockBufferForCleanupand pool-worker ring teardown; the recovery path was the one place that issued prefetch reads without ever collecting them.Verification (x86_64, Ubuntu 24.04,
io_method=sync, defaultrecovery_prefetch=try)kill -9, restart,DROP TABLEpg_buffercachepinned buffers of the relation after recovery, idle serverCHECKPOINT,kill -9, restart,DROPkill -9, restart,DROPDROPcargo test --release -p xlogrecovery -p bufmgrSetting
recovery_prefetch=offalso avoids the bug on the unpatched build, which independently confirms the prefetcher as the source.🤖 Generated with Claude Code
Summary by CodeRabbit