buckethead.storage.lifecycle¶
Flush-loop daemon thread + signal / atexit handlers.
buckethead.storage.lifecycle
¶
Flush-loop thread, signal handlers, atexit wiring.
The pattern here is a direct port of spikes/signals_atexit.py (Phase 0 Validation #3 + #4). Three principles, in order of importance:
- Signal handlers never do I/O. They set a flag. The main thread does
the real work via
BucketSQLite.stop(). - The flush loop is a daemon thread. Its stop is event-driven (not a
polling sleep), so
stop()returns promptly even if the interval is long. - Everything is idempotent: stop can be called twice, teardown can be called twice, a signal can arrive after atexit has already run.
FlushLoop
¶
FlushLoop(
flush_callable: Callable[[], None],
interval_seconds: float,
*,
on_error: Callable[[BaseException], None] | None = None,
)
Daemon thread that calls flush_callable every interval_seconds.
Exceptions inside flush_callable are reported via on_error (if
provided) and otherwise logged at WARNING; they never terminate the
loop. Exceptions inside on_error itself are also suppressed — no
user code can kill the thread.
Source code in src/buckethead/storage/lifecycle.py
install_signal_handlers
¶
Install SIGTERM + SIGINT handlers that call on_signal(signum).
The caller is responsible for everything else (flushing, restoring default disposition, re-raising). Returns a teardown closure that restores the previous handlers exactly.
Must be called from the main thread — Python's signal.signal
rejects calls from other threads.
Source code in src/buckethead/storage/lifecycle.py
install_atexit
¶
Register on_exit as an atexit hook. Returns a teardown closure.
atexit is the last-resort safety net for sys.exit() paths that don't involve a signal. Signal paths should not rely on atexit running — on some signals (SIG_DFL termination) atexit is bypassed.