Replicant.Spill (Replicant v0.1.0)

Copy Markdown View Source

The single File.* boundary for consumer disk spill (spec §5/§6). Owns the per-slot spill directory (0700), the per-top-xid spill file (0600), append of length-prefixed :erlang.term_to_binary({subxid, %Change{}}) frames, file discard, and the per-slot startup sweep. Every I/O or serialization fault is scrubbed to a value-free {:error, %Replicant.Error{reason: :spill_io_failed}} here (Critical Rule 1) — no path, frame, or row value ever escapes. Spill files are scratch: never fsync'd, deleted on commit/abort/reset/halt, and (for a prior abnormal exit) swept per-slot on that slot's next start.

Summary

Functions

Append one {subxid, %Change{}} frame; returns the frame's byte size.

Close the device WITHOUT removing the file (the reader re-opens for a single read pass).

Close the device AND delete the file (commit-after-delivery / abort / reset / halt).

Open (create+truncate) the spill file for top_xid under base_dir/slot_name (0700 dir, 0600 file).

Delete a spill file at path if it exists (used when only the path is retained). Best-effort, value-free.

Sweep THIS slot's spill subdir on the slot's pipeline start — removes any file a prior abnormal exit left (spec §5). Per-slot, NOT global: a global sweep would delete a concurrently-running slot's active files. Best-effort and value-free.

Types

handle()

@type handle() :: %{device: :file.io_device(), path: String.t()}

Functions

append(map, subxid, change)

@spec append(handle(), non_neg_integer(), Replicant.Change.t()) ::
  {:ok, pos_integer()} | {:error, Replicant.Error.t()}

Append one {subxid, %Change{}} frame; returns the frame's byte size.

close(map)

@spec close(handle()) :: :ok

Close the device WITHOUT removing the file (the reader re-opens for a single read pass).

discard(map)

@spec discard(handle()) :: :ok

Close the device AND delete the file (commit-after-delivery / abort / reset / halt).

open(base_dir, slot_name, top_xid)

@spec open(String.t(), String.t(), non_neg_integer()) ::
  {:ok, handle()} | {:error, Replicant.Error.t()}

Open (create+truncate) the spill file for top_xid under base_dir/slot_name (0700 dir, 0600 file).

rm(path)

@spec rm(String.t()) :: :ok

Delete a spill file at path if it exists (used when only the path is retained). Best-effort, value-free.

sweep_slot(base_dir, slot_name)

@spec sweep_slot(String.t(), String.t()) :: :ok

Sweep THIS slot's spill subdir on the slot's pipeline start — removes any file a prior abnormal exit left (spec §5). Per-slot, NOT global: a global sweep would delete a concurrently-running slot's active files. Best-effort and value-free.