Skip to content

GetUpdatesSince: Fix assert when polling an empty DB#14865

Open
evanj wants to merge 1 commit into
facebook:mainfrom
evanj:evan.jones/wal-iterator-polling
Open

GetUpdatesSince: Fix assert when polling an empty DB#14865
evanj wants to merge 1 commit into
facebook:mainfrom
evanj:evan.jones/wal-iterator-polling

Conversation

@evanj

@evanj evanj commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

TransactionLogIterator supports polling the end of the WAL e.g. db_log_iter_test.cc test TransactionLogIteratorStallAtLastRecord. To poll, we need to call Next() on an iterator that is at the end (iter->Valid() == false). However, if the iterator is created when there are no WAL files (e.g. an empty DB), the call to Next() hits an assert in NextImpl:

assert(current_log_reader_);

To fix this, make an iterator without files return a TryAgain status code instead. This should not affect any existing applications, since if they are doing this, they will crash.

transaction_log_impl.cc:

  • TransactionLogIteratorImpl::NextImpl: Check for no files and return TryAgain.
  • db.h: GetUpdatesSince doc comment: Reference the documentation on TransactionLogIterator.
  • transaction_log.h: Clarify how to use TransactionLogIterator.

db_log_iter_test:

  • OpenTransactionLogIter: Remove EXPECT_TRUE(iter->Valid()): this is not true if the database is empty.
  • TransactionLogIteratorPollNoWALFiles: Test polling the iterator in two cases: the database is empty, and all WAL files were purged. These previously triggered an assertion in Next().

@meta-cla meta-cla Bot added the CLA Signed label Jun 18, 2026
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown

✅ clang-tidy: No findings on changed lines

Completed in 36.8s.

@evanj evanj force-pushed the evan.jones/wal-iterator-polling branch 2 times, most recently from 9ca6b75 to f59cce7 Compare June 18, 2026 20:36
TransactionLogIterator supports polling the end of the WAL e.g.
db_log_iter_test.cc test TransactionLogIteratorStallAtLastRecord. To
poll, we need to call Next() on an iterator that is at the end
(iter->Valid() == false). However, if the iterator is created when
there are no WAL files (e.g. an empty DB), the call to Next() hits
an assert in NextImpl:

  assert(current_log_reader_);

To fix this, make an iterator without files return a TryAgain status
code instead. This should not affect any existing applications, since
if they are doing this, they will crash.

transaction_log_impl.cc:
* TransactionLogIteratorImpl::NextImpl: Check for no files and return
  TryAgain.
* db.h: GetUpdatesSince doc comment: Reference the documentation on
  TransactionLogIterator.
* transaction_log.h: Clarify how to use TransactionLogIterator.

db_log_iter_test:
* OpenTransactionLogIter: Remove EXPECT_TRUE(iter->Valid()): this is
  not true if the database is empty.
* TransactionLogIteratorPollNoWALFiles: Test polling the iterator in
  two cases: the database is empty, and all WAL files were purged.
  These previously triggered an assertion in Next().
@evanj evanj force-pushed the evan.jones/wal-iterator-polling branch from f59cce7 to 294124c Compare June 25, 2026 16:24
@github-actions

Copy link
Copy Markdown

🟡 Codex Code Review

Auto-triggered after CI passed — reviewing commit 294124c


Codex review failed before producing findings.

WARNING: proceeding, even though we could not create PATH aliases: Refusing to create helper binaries under temporary dir "/tmp" (codex_home: AbsolutePathBuf("/tmp/codex-home"))
error: the argument '--base <BRANCH>' cannot be used with '[PROMPT]'

Usage: codex exec review --commit <SHA> --base <BRANCH> --title <TITLE> --model <MODEL> --config <key=value> --dangerously-bypass-approvals-and-sandbox --output-last-message <FILE> [PROMPT]

For more information, try '--help'.

ℹ️ About this response

Generated by Codex CLI.
Review methodology: claude_md/code_review.md

Limitations:

  • Codex may miss context from files not in the diff
  • Large PRs may be truncated
  • Always apply human judgment to AI suggestions

Commands:

  • /codex-review [context] — Request a code review
  • /codex-query <question> — Ask about the PR or codebase

@github-actions

Copy link
Copy Markdown

✅ Claude Code Review

Auto-triggered after CI passed — reviewing commit 294124c


Summary

Clean, minimal fix for a real crash (assertion failure when polling an empty-DB TransactionLogIterator). The approach is correct: returning TryAgain is consistent with the existing polling protocol and all callers handle it properly. Documentation improvements are accurate and valuable.

High-severity findings (0):

No high-severity findings.

Full review (click to expand)

Findings

🔴 HIGH

None.

🟡 MEDIUM

M1. Removing EXPECT_TRUE(iter->Valid()) from OpenTransactionLogIter weakens existing tests -- db/db_log_iter_test.cc:31
  • Issue: The EXPECT_TRUE(iter->Valid()) assertion was removed from the shared helper OpenTransactionLogIter. While necessary for the new empty-DB test, it removes a validity check exercised by 10+ existing call sites that all expect the iterator to be valid. All those tests write data before calling the helper, so the assertion should always pass for them.
  • Root cause: The helper now serves two roles: empty-DB testing (Valid()==false) and normal testing (Valid()==true).
  • Suggested fix: Add an optional expect_valid parameter (default true) to the helper, or add ASSERT_TRUE(iter->Valid()) at each existing call site, or create a separate helper for the empty-DB case.
M2. TryAgainStatus() uses unnecessary static local -- db/transaction_log_impl.cc:176-179
  • Issue: static const char* const msg = "..." inside TryAgainStatus() is safe but unnecessary -- string literals already have static storage duration. Passing the literal directly to Status::TryAgain() is simpler and more consistent with the rest of the codebase.
  • Suggested fix: Either inline the literal (return Status::TryAgain("Create a new iterator to fetch the new tail.");) or use a file-level constexpr const char* if sharing between the two call sites is desired.

🟢 LOW / NIT

L1. Next() polling docs could mention TryAgain is terminal -- include/rocksdb/transaction_log.h
  • Issue: The Next() method comment says it can poll when status() == OK, but doesn't mention that after TryAgain, the iterator is permanently stuck. The class-level comment covers this, but repeating at the method level would help.
L2. Comment capitalization -- db/transaction_log_impl.cc:183
  • Issue: New comment starts lowercase ("if the iterator has no files..."). Most multi-line comments in this file use sentence case.

Cross-Component Analysis

All callers (JNI, db_repl_stress, db_stress, tests) are safe. The fix only triggers on a previously-crashing code path, so no existing behavior changes for working callers. WritePrepared/WriteUnprepared transactions don't use this API (returns NotSupported). ReadOnly DB is safe.

Assumption stress test: All 5 key invariant claims verified correct. files_ is never modified after construction, started_ is provably false in the guarded branch, internal callers skip the new check, and the size_t underflow at line 207 is unreachable with the fix in place.

Positive Observations

  • Minimal, focused fix (6 lines of logic) for a real crash
  • Semantically correct status code (TryAgain matches existing polling protocol)
  • Thorough test covering both empty-DB and post-purge scenarios
  • Valuable documentation upgrade for the previously underdocumented polling protocol

ℹ️ About this response

Generated by Claude Code.
Review methodology: claude_md/code_review.md

Limitations:

  • Claude may miss context from files not in the diff
  • Large PRs may be truncated
  • Always apply human judgment to AI suggestions

Commands:

  • /claude-review [context] — Request a code review
  • /claude-query <question> — Ask about the PR or codebase

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant