Function: readall_records in write-ahead-log/wal.py

Date: 2026-05-29

Time: 06:30

readall_records — WAL Record Iterator

Purpose

readall_records is the internal read path for the WAL. It iterates over every WAL segment file in sequence order, deserializes each binary record, and yields them one at a time. It exists to provide a single stream abstraction over what is physically a set of numbered .wal files on disk.

It's the foundation for two public operations: replay() (crash recovery) and iterate() (raw inspection).

Contract

Parameters

None — it reads self.dir implicitly via self.wal_files().

Return Value

Iterator[WALRecord] — a lazy generator. Records include all operation types (PUT, DELETE, COMMIT, CHECKPOINT). The caller is responsible for filtering; replay() filters to PUT/DELETE, for example.

The iterator terminates in one of two ways:

1. Clean exhaustion — all files read, all records yielded.

2. Early termination on corruption — a CRC mismatch in any file stops the entire iterator immediately, even if later files are clean.

Algorithm


for each WAL file (sorted by filename):
    open the file in binary mode
    loop:
        try to read one record
        if EOF or partial record → break (move to next file)
        if valid → yield it
        if CRC mismatch → return (stop everything)

The key distinction is break vs return:

This is a deliberate design choice. A corrupt record means the write was interrupted (crash, power loss). Any records after the corruption point — including in subsequent files — cannot be trusted because they may depend on state that was never committed. This is the standard WAL recovery rule: stop at the first sign of damage.

Side Effects

Error Handling

| Source | Error | Behavior |

|--------|-------|----------|

| readrecord returns None | EOF / partial read | break — advance to next file |

| readrecord raises ValueError | CRC mismatch | return — stop the entire iterator |

| File doesn't exist / permission error | OSError | Not caught — propagates to caller |

The ValueError swallowing is intentional and correct for crash recovery: corruption marks the end of the reliable log. But note that filesystem errors (missing files, permission denied) are *not* caught — those indicate operational problems, not crash recovery scenarios.

Usage Patterns

Called in two places:

1. replay(after_seq) — filters to PUT/DELETE records past a given sequence number. This is the crash recovery path.

2. iterate() — yields all raw records including COMMIT and CHECKPOINT. Used for inspection/debugging.

Both callers flush the write fd first. readallrecords does not acquire self.lock — the callers handle locking where needed (replay doesn't hold the lock during iteration; iterate releases it after flushing).

Dependencies

Assumptions Not Enforced by Types

1. WAL files are not modified concurrently by another process during iteration. There's no file locking.

2. walfiles() returns files in write order. This relies on filenames being zero-padded integers. If someone manually creates foo.wal, it would sort unpredictably.

3. The write fd has been flushed. The method itself doesn't flush — it trusts the caller.

4. Corruption only happens at the tail. The stop-on-first-corruption strategy assumes damage is from an interrupted write at the end, not a bit-flip in the middle of an otherwise valid file. A mid-file bit-flip would silently discard all subsequent valid records.

Beliefs