Skip to main content

RecordLog

Struct RecordLog 

Source
pub struct RecordLog { /* private fields */ }
Expand description

Append-only framed record log.

§Failure model

Mutating operations (append, append_record, fsync, rotate) may fail in the middle of an I/O operation, after the kernel has accepted part of a frame but before the whole frame is on disk (ENOSPC, a broken disk, a torn write). When that happens the log handle enters a poisoned state:

  • Every subsequent mutating call returns a deterministic error whose message starts with datawal: writer poisoned: and ends with ; drop handle and reopen. The error is intentionally a plain anyhow::Error in 0.1.x; promotion to a typed error variant is tracked for a future minor release.
  • Read-only operations (scan, scan_iter, recovery_report, active_segment, dir) remain available so the caller can inspect state before dropping the handle.
  • The caller must drop the handle and re-open the directory with RecordLog::open. Reopen uses the standard longest-valid-prefix recovery (see invariant 2 in AGENTS.md) and will discard any partial tail bytes left behind by the failed write.

The crate intentionally does not try to truncate the partial tail or resync active_size on the live handle. Both are forms of mutating state after a write failure, which expands rather than contains the blast radius.

Implementations§

Source§

impl RecordLog

Source

pub fn open(dir: &Path) -> Result<Self>

Open (or create) a record log rooted at dir.

Steps:

  1. mkdir -p dir.
  2. Acquire an exclusive OS-level advisory lock on <dir>/.lock (held by a file descriptor; released automatically when this RecordLog is dropped or when the holding process exits).
  3. Discover segments; if none, create segment id 1.
  4. Pick the highest id as the active segment.
  5. Scan all segments to discover next_txid and store the recovery report.
  6. Open the active segment for append.

Fails fast if another RecordLog is already open on the same directory (the kernel-level lock acquisition does not block).

Source

pub fn is_poisoned(&self) -> bool

Returns true if the writer is poisoned by a prior I/O failure.

A poisoned log refuses all further mutating operations. Read-only operations remain available so the caller can inspect state before dropping the handle. See the type-level “Failure model” docs.

Source

pub fn dir(&self) -> &Path

Directory backing this log.

Source

pub fn active_segment(&self) -> u32

Active segment id.

Source

pub fn recovery_report(&self) -> Result<RecoveryReport>

Last recovery report computed by open() or scan().

Source

pub fn append(&mut self, payload: &[u8]) -> Result<RecordRef>

Append an opaque payload as a Raw record.

Durability boundary. This call writes a framed, CRC-protected record to the active segment’s file via write_all. It does not fsync the file or the directory. The record is recoverable (a subsequent scan() will return it) as long as the OS does not lose the buffered write, but it is not yet durable across a power failure or hard crash of the host until fsync() returns successfully.

Pattern for “this must survive a crash”:

log.append(payload)?;
log.fsync()?;
Source

pub fn append_record( &mut self, record_type: RecordType, key: &[u8], payload: &[u8], ) -> Result<RecordRef>

Append a typed record with a key and a payload.

Used by crate::DataWal for Put / Delete. Length limits are validated by the encoder before allocation.

Same durability semantics as RecordLog::append: framed and recoverable on return, but only durable after a successful RecordLog::fsync.

Source

pub fn scan(&mut self) -> Result<Vec<Record>>

Scan every segment in order and return every valid record.

Materialises every record into a Vec<Record>. For logs with many records or large payloads, prefer RecordLog::scan_iter which yields one record at a time without materialising the whole log.

Also refreshes recovery_report() and the internal next_txid.

Source

pub fn scan_iter(&self) -> Result<RecordIter<'_>>

Returns an iterator over records.

This is lazy at the record level: callers can pull one record at a time without materialising the whole log into a Vec<Record>. It is not a chunked or zero-copy scanner — v0.1 loads one segment at a time into memory before yielding records from it. Peak memory is therefore bounded by the size of the largest segment, not by the total log size.

Recovery semantics match RecordLog::scan:

  • A truncated or CRC-bad tail on the last segment is tolerated and ends iteration cleanly. The amount of trailing garbage discarded is reflected in RecordIter::recovery_report.
  • Any structural decode error, or any CRC/truncation problem in a sealed (non-last) segment, is yielded as an Err item; iteration ends after that error, and the underlying error is the same anyhow error that RecordLog::scan would have returned.

This method takes &self. It does not refresh the log’s own recovery_report() or next_txid — only RecordLog::scan does that.

Aborting iteration early (by dropping the iterator before exhaustion) is supported and has no on-disk side effects.

Source

pub fn fsync(&mut self) -> Result<()>

Force durability of all records appended so far.

On successful return, every record passed to append / append_record since this RecordLog was opened (or since the last fsync returned) is durable: it will survive a process crash, kernel panic or power loss on the underlying disk, modulo the usual filesystem caveats (working fsync syscall, no lying disk cache).

Internally this calls File::sync_all on the active segment and fsync on the containing directory, so that segment creations and rotations are also durable.

fsync may be called as often as desired; on a log with no new appends since the last fsync it is effectively a no-op at the kernel level, but it is always safe.

Source

pub fn rotate(&mut self) -> Result<()>

Rotate to the next segment. The current segment is closed and fsynced; the new segment is created empty and becomes active.

Source

pub fn close(self) -> Result<()>

Close the log, releasing the directory lock.

Trait Implementations§

Source§

impl Debug for RecordLog

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.