pub struct FileLog { /* private fields */ }Expand description
Second implementation (seam rule: feature-poor is fine, broken is not): JSON-lines file, append-only, rebuilt head on open.
Implementations§
Source§impl FileLog
impl FileLog
Sourcepub fn open(path: &Path) -> Result<Self, LogError>
pub fn open(path: &Path) -> Result<Self, LogError>
Opens (creating if absent) the log file at path and replays it to
rebuild the in-memory index and head.
§Torn tails
A power cut can leave the file ending in a record that was only partly written, or — under delayed allocation — in a run of NUL bytes. Any final record with no terminating newline is treated as such a torn write and truncated away, whether or not it happens to decode: a complete record whose newline never landed would otherwise be concatenated with the next append into one line that never parses again.
Discarding it cannot lose an acknowledged op. The sequencer
acknowledges only after OpLog::sync returns, and that call
returns only once every byte before it is on the platter, so
anything in an unterminated tail was never acknowledged to anyone.
The truncation is reported by FileLog::torn_tail_bytes rather
than performed silently.
A decode failure in a newline-terminated record is not a torn
write — it is damage to a record that was once written whole — and
still fails as LogError::Corrupt. That includes a NUL-filled
gap followed by further records: refusing to start is the right
answer there, because the alternative is silently dropping ops from
the middle of the log.
§Errors
Returns LogError::Io on filesystem failure and
LogError::Corrupt when a terminated line fails to decode.
Sourcepub fn torn_tail_bytes(&self) -> u64
pub fn torn_tail_bytes(&self) -> u64
Bytes of partly written tail that FileLog::open truncated away,
or 0 if the log ended on a record boundary.
Non-zero means this process started after an unclean stop. The
discarded bytes were never acknowledged (see FileLog::open), so
this is a fact worth reporting, not a fault — but a caller that
never reports it turns a crash into a silent one.
Sourcepub fn torn_tail_quarantine(&self) -> Option<&Path>
pub fn torn_tail_quarantine(&self) -> Option<&Path>
Where the truncated bytes were saved, if any were.
The truncation is automatic because a node has to come back up
unattended after a power cut, but the bytes are not thrown away:
an operator asking “what was lost” gets a file to look at rather
than a number. None means the log ended on a record boundary.
Trait Implementations§
Source§impl Drop for FileLog
impl Drop for FileLog
Source§fn drop(&mut self)
fn drop(&mut self)
Last-resort flush. The sequencer syncs per batch, so in normal
operation this finds an empty buffer; it exists so a log dropped on
an error path does not silently discard buffered entries. Errors
are unreportable here, hence the ok() — durability is the
sequencer’s job via OpLog::sync, not this.
Source§impl OpLog for FileLog
impl OpLog for FileLog
Source§fn get(&self, seq: u64) -> Option<OpEntry>
fn get(&self, seq: u64) -> Option<OpEntry>
Reads one entry back: from the pending buffer if it has not been flushed yet, otherwise from the file at its recorded offset.
Returns None for an out-of-range seq and also for a stored line
that fails to decode or read. The trait signature has no way to say
“present but unreadable”, and inventing one is a wider change than
this belongs in — but a corrupt log is a real condition, and open
does report it as LogError::Corrupt, so damage is caught when
the log is next opened rather than never.
Source§fn sync(&mut self) -> Result<(), LogError>
fn sync(&mut self) -> Result<(), LogError>
Flush the buffer to the OS, then ask the OS to put it on the
platter. Both halves are required and neither substitutes for the
other: flush alone leaves the bytes in the page cache, and
sync_data alone would sync a buffer that was never written.
sync_data rather than sync_all: the file’s length and contents
must survive, its mtime need not, and skipping the metadata write
is the cheaper half of an fsync.
Source§fn append(&mut self, entry: OpEntry) -> Result<ContentHash, LogError>
fn append(&mut self, entry: OpEntry) -> Result<ContentHash, LogError>
entry and returns its content hash (the new head). Read moreSource§fn head(&self) -> Option<ContentHash>
fn head(&self) -> Option<ContentHash>
None for an empty log.