Date: 2026-05-29
Time: 06:36
WriteAheadLog._rotate_rotate closes the current WAL segment file and opens a fresh one with a sequentially numbered filename. WAL implementations split their log across multiple files (segments) so that old, fully-checkpointed segments can be deleted without rewriting the active file. This method is the mechanism that creates the next segment in that chain.
Preconditions:
self.dir exists on disk (guaranteed by init_ calling os.makedirs).self.lock (every call site — openlatest, mayberotate, and indirectly append/appendbatch/checkpoint/truncate — acquires the lock before reaching _rotate).Postconditions:
self.currentfile points to a new path of the form {dir}/{NNNNNN}.wal.self._fd is an open file descriptor in append-binary mode to that new file.open(..., "ab")).Invariants:
None — this is a private method operating entirely on instance state.
None. All effects are via mutation of self.fd and self.current_file.
1. Close the old segment. If self._fd is not None, flush the userspace buffer, call os.fsync to force the kernel buffer to durable storage, then close the file descriptor. This ensures no data from the prior segment is lost.
2. Determine the next filename. List all existing .wal files (sorted lexicographically, which equals numerically due to zero-padding). If any exist, parse the highest-numbered filename by splitting on "." and taking the integer prefix, then add 1. If no files exist, start at 1.
3. Open the new segment. Construct the path as {next_num:06d}.wal (e.g., 000001.wal, 000002.wal) and open it in append-binary mode. Store both the path and the fd on the instance.
fsync on the old file, creation and open of a new file.self.fd and self.current_file are replaced.No exceptions are caught. If os.fsync or open fails (e.g., disk full, permission denied), the exception propagates to the caller. This is appropriate — a WAL that can't write is a fatal condition.
There's one subtle assumption: if self.fd is non-None but already closed (e.g., due to a bug elsewhere), calling self.fd.flush() will raise ValueError. The code trusts that _fd is either None or a valid open descriptor.
_rotate is called from two places:
1. openlatest — at startup, when no WAL files exist or the latest one is already at capacity. This is the "create the very first segment" path.
2. mayberotate — after each write operation, when self.fd.tell() >= self.maxfilesize. This enforces the segment size limit.
Callers never call _rotate directly from outside the class (it's private by convention).
os.fsync, os.path.basename, os.path.join, os.listdir — filesystem operations.self.walfiles() — returns the sorted list of existing segment paths. The filename-parsing logic in rotate must stay consistent with the naming scheme wal_files expects (files ending in .wal, sorted lexicographically)..wal files in the same directory, _rotate could collide on filenames. The threading lock only protects in-process concurrency._rotate assumes every .wal file in the directory follows the NNNNNN.wal convention. A file named notes.wal would cause int(...) to raise ValueError.fd truthiness equals open-ness. The if self.fd guard assumes a non-None fd is still open. There's no explicit check for fd.closed.