codexfile-system-journal-crash-state-recovery.py
file-system-journal-crash-state-recovery.py
"""Exhaust the stated three-record crash model and draw its recovery table.
Tested with Python 3.14, matplotlib 3.10.7 and numpy 2.3.5.
Run from _media. Records are atomic, flushes honored, and there is one writer.
"""
from collections import deque
from pathlib import Path
import matplotlib.pyplot as plt
def successors(state):
journal, commit, home, retired = state
if retired:
return []
if not commit:
steps = [(journal | (1 << i), 0, 0, False) for i in range(3)
if not journal & (1 << i)]
if journal == 7:
steps.append((7, 1, 0, False))
return steps
steps = [(7, 1, home | (1 << i), False) for i in range(3)
if not home & (1 << i)]
if home == 7:
steps.append((0, 0, 7, True))
return steps
def reachable_states():
initial = (0, 0, 0, False)
seen, pending = {initial}, deque([initial])
while pending:
for state in successors(pending.popleft()):
if state not in seen:
seen.add(state)
pending.append(state)
return seen
def recover(state):
journal, commit, home, retired = state
if commit:
assert journal == 7 and not retired
return 7
return home
def triple(mask):
return "".join(str((mask >> i) & 1) for i in range(3))
def main():
states = reachable_states()
assert len(states) == 17
assert all(recover(state) in (0, 7) for state in states)
assert sum(recover(state) == 0 for state in states) == 8
assert sum(recover(state) == 7 for state in states) == 9
masks = sorted(range(8), key=triple)
ordered = [(j, 0, 0, False) for j in masks]
ordered += [(7, 1, h, False) for h in masks]
ordered += [(0, 0, 7, True)]
assert set(ordered) == states
fig, ax = plt.subplots(figsize=(11.2, 8.8), dpi=100, facecolor="white")
ax.axis("off")
fig.subplots_adjust(left=0.04, right=0.96, bottom=0.17, top=0.85)
fig.text(0.5, 0.955, "Every crash state of a three-record redo journal",
ha="center", fontsize=18, weight="bold", color="#15354a")
fig.text(0.5, 0.911, "Triples are I, B, D; 1 means the new record is durable",
ha="center", fontsize=12, color="#435667")
columns = ["Crash phase", "Journal\npayload", "Durable\ncommit", "Final records\nbefore recovery", "Recovery action", "Final records\nafter recovery"]
rows = []
for j, c, h, retired in ordered:
phase = "Retired" if retired else ("After commit" if c else "Before commit")
action = "Already complete" if retired else ("Replay I + B + D" if c else "Ignore payload")
rows.append([phase, "—" if retired else triple(j), str(c), triple(h), action,
triple(recover((j, c, h, retired)))])
table = ax.table(cellText=rows, colLabels=columns, cellLoc="center", loc="center",
colWidths=[0.16, 0.12, 0.11, 0.19, 0.23, 0.19], bbox=[0, 0, 1, 1])
table.auto_set_font_size(False)
table.set_fontsize(11)
for (row, col), cell in table.get_celld().items():
cell.set_edgecolor("#c9d4dc")
if row == 0:
cell.set_facecolor("#15354a")
cell.set_text_props(color="white", weight="bold")
cell.set_height(0.088)
else:
cell.set_facecolor("#edf3f8" if row <= 8 else ("#e4f3ed" if row <= 16 else "#e8e9ef"))
if col in (1, 2, 3, 5):
cell.set_text_props(fontfamily="DejaVu Sans Mono")
if col == 5:
cell.set_text_props(weight="bold")
fig.text(0.5, 0.12, "8 pre-commit states + 8 checkpoint states + 1 retired state = 17",
ha="center", fontsize=12, weight="bold", color="#15354a")
fig.text(0.5, 0.083, "Atomic records • payload flush before commit • commit flush before final writes",
ha="center", fontsize=11, color="#435667")
fig.text(0.5, 0.052, "Final copies flushed before retirement • intact durable storage • recovery before access",
ha="center", fontsize=11, color="#435667")
fig.text(0.5, 0.022, "The table lists abstract durable states, not measurements of a real filesystem.",
ha="center", fontsize=10, color="#435667")
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()