Files
photoanalyzer/photo_pipeline/faults.py

46 lines
1.8 KiB
Python

"""Test-only fault control points (concept §18, US07-04).
Crash safety can only be proven by crashing at the exact moment a transition has
been persisted but its consequence has not. That needs a barrier *inside* the
production code path — but not a production capability: there is no endpoint, no
service method, and no configuration file entry that can trigger one. The only
switch is an environment variable naming a single point, read at the moment it is
passed, and the only thing it does is kill the process. A deployment that never
sets it can never reach the barrier.
``os._exit`` is deliberate: it skips atexit handlers, buffered flushes, and
``finally`` blocks, which is what a real ``SIGKILL`` or power loss does. A clean
shutdown would prove nothing.
The points are the persisted transitions of the journalled stages:
rename moving | moved | database_updated | verified | complete
archive transferring | verified | removing | source_removed | complete
exif exif:written — keywords on disk, checkpoint not yet recorded
upload upload:accepted — uploader exited, outcome not yet persisted
jobs job:item_done — item committed, job outcome not yet written
Recovery for each is asserted in tests/integration/test_fault_matrix.py and
tests/e2e/test_crash_recovery.py.
"""
from __future__ import annotations
import os
ENV_VAR = "PHOTO_PIPELINE_FAULT_AFTER"
EXIF_WRITTEN = "exif:written"
UPLOAD_ACCEPTED = "upload:accepted"
JOB_ITEM_DONE = "job:item_done"
def maybe_fault(point: str) -> None:
"""Die abruptly when ``PHOTO_PIPELINE_FAULT_AFTER`` names ``point``.
Shared by the rename, archive, restore, EXIF, upload, and job lanes, each
passing its own state names. Never set the variable outside tests.
"""
if os.environ.get(ENV_VAR) == point:
os._exit(9)