Inspecting the disk at the moment of failure
When a test fails after a crash, the cause is often what had and had not reached the disk. seedfall inspect shows every node's files at the moment of failure: which bytes were safely saved, which a crash would lose, and where a write was cut short. Use it for durability bugs, such as data that is missing after a restart.
seedfall replay tells you what happened. seedfall inspect shows you what is on disk when it goes wrong: every file on every node, how much of it is durable, which bytes a crash would lose, which directory entries are not durable yet, and where the last crash tore a write.
seedfall inspect -c rosedb.json -seed 1
seed 1 failed at t=3.12130224s: client#1: acknowledged entry 27 lost: server now answers "VAL e141-1111111111111111111111"
disk state at t=3.12130224s, the moment of failure
node server (directory entries are durable once made; torn_writes: a crash may also keep a prefix of the pending writes)
path size durable state
data/ - - durable
wal/ - - durable
000000001.SEG 4029 4029 durable
^ torn by the crash at t=427.268199ms: kept 60 of a 73-byte write at offset 2019
000000002.SEG 3979 3979 durable
000000003.SEG 2054 2054 durable
2 dirs, 3 files: 10062 bytes durable, 0 bytes pending, 0 entries lost on crash, 0 removed entries that come back
node client (directory entries are durable once made; torn_writes: a crash may also keep a prefix of the pending writes)
empty disk
The crash at 427ms left a 60-byte fragment of entry 26 at offset 2019, and every later entry was appended after it. That is the rosedblabs/wal bug in findings, visible without reading any code.
Usage
seedfall inspect [-c seedfall.json] -seed N [-faults file.json] [-at DURATION] [-node name] [-cat path | -out dir]
| Flag | Meaning |
|---|---|
-seed |
Seed to replay |
-faults |
Replay with a fault schedule from seedfall shrink |
-at |
Snapshot at this virtual time instead of at the failure, for example -at 4.4s. The snapshot is taken at the last scheduler step at or before it. If the run fails or ends first, the snapshot is taken there. |
-node |
Only this node |
-cat |
Print the durable content of one file: text as is, binary as a hex dump. The file must exist in the durable image. |
-out |
Write each node's durable disk image to dir/<node>/, so you can open it with the library's own tools, or start new runs from it with "disk": "dir/<node>", a path relative to the config's directory (seeded disks); the command prints that path. The target must not exist yet. |
The run is the same as seedfall replay: same seed, same faults, same trace. Inspecting changes nothing about it.
Reading the output
Each file has its live size (what the program reads now) and its durable size (what survives a crash), and one state:
| State | Meaning |
|---|---|
durable |
The live content and the durable content are the same, and the name survives a crash |
PENDING: N bytes lost on crash at [a,b) ... |
These byte ranges of the live content differ from the durable content: they were written but not fsynced |
PENDING truncate |
The file was truncated but not fsynced; a crash restores the old bytes |
NOT DURABLE: never fsynced |
Without strict_dirs, a file that was never fsynced disappears on crash |
NOT DURABLE: entry vanishes on crash |
With strict_dirs, the directory entry is not durable until the parent directory is fsynced, even if the file's data is |
A ^ torn by the crash at t=... line under a file says the last crash kept only part of one write: how many bytes of it, and at which offset. Those partial bytes are now durable and are what recovery code has to cope with.
With reorder_writes, a ^ the crash at t=... wrote back pages out of order line lists the writes that were pending at the last crash, numbered in program order, and says which of them reached the disk: kept, lost, or partial when only some of its pages did. A later write marked kept above an earlier one marked lost is the reordering your recovery code has to survive. The same list is in last_writeback in the MCP inspect output.
With the disk faults, a ^ corrupted ranges list under a file shows every range a disk fault damaged: a lost write or misdirected write that a successful fsync left with its old content, a bit flipped by bit rot or by crash corruption, and each corrupted read that returned a flipped bit while the disk held the right bytes. It is a history: an entry stays after the program overwrites the range or a later fsync rewrites the page in full. The last 8 are shown, and the full list is in damage in the MCP inspect output. With disk_bytes, a disk_bytes: N of M bytes used line follows the totals.
kv.log 5727 5727 durable
^ corrupted ranges:
t=5.259168049s misdirected write: [4096,5005) kept its old content on disk, and its page landed at offset 0 instead
With strict_dirs, a file that was unlinked or renamed away while its parent directory was not fsynced is listed separately as an entry that a crash brings back. Renaming a temporary file over an existing one without fsyncing the directory shows up as the new name NOT DURABLE and the old content coming back.
The durable image is the disk a crash would leave without torn_writes or reorder_writes. With torn_writes, a crash can also keep an in-order prefix of the pending writes and, with strict_dirs, of the pending directory operations. With reorder_writes, what it can keep depends on fs_model, which the node line names: fs_model is also a field of the MCP inspect output, and each file reports symlink and links there. With inspect -out, a symlink is written as a symlink; on Windows without the privilege to create one, it is written as a file holding its target. Hard links are written as separate files.
Examples
NATS JetStream, the stream that vanishes (finding):
cd examples/natsjs
seedfall inspect -c seedfall.always.json -seed 40 -faults metasum.faults.json
EVENTS/ - - durable
meta.inf 517 517 durable
meta.sum.tmp 6 6 durable
^ torn by the crash at t=158.20724ms: kept 6 of a 16-byte write at offset 0
msgs/ - - durable
1.blk 7702 7702 durable
meta.inf exists, meta.sum does not, and only a torn meta.sum.tmp is left. NATS skips a stream without a checksum on restart, and because meta.inf exists it never writes meta.sum again, so the 1.blk messages acknowledged since are skipped on every later restart.
tidwall/buntdb, one step before the crash that empties the database (finding):
cd examples/buntdb
seedfall inspect -c seedfall.shrink.json -seed 27 -at 4.417s -node server
data/ - - durable
db 1255 0 NOT DURABLE: never fsynced, vanishes on crash
The AOF shrink renamed its rewritten file into place without fsyncing it: 1255 bytes live, none durable. At the failure, db holds only the 211 bytes that the crash at 4.417s happened to keep.
From an agent
The MCP server exposes the same snapshot as seedfall_inspect, with the per-file sizes, volatile byte ranges, entry durability and last tear as JSON, and optionally one file's durable content. See agents.md.
Limitations
- The snapshot covers the simulated disk only, not process memory or in-flight network messages.
seedfall debug -atstops the run at the same moment and also shows the messages in flight, the active faults and each node's live file content (timeline.md). - The last tear is recorded per file. A later write over the same range does not clear it.
-catprints the whole file. Pipe it throughtailfor large files.