mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-22 04:41:23 +00:00
lib/demo_mode.py (342). server.py 1,870 -> 1,649.
The read-only request guard (its 96-entry blocked-route table + the middleware) and the
hourly session janitor (registry, hook runner, thread). Bodies VERBATIM.
THE MIDDLEWARE NEEDS `app`, SO THE MODULE TAKES IT. _demo_mode_guard is an
@app.middleware("http") and cannot exist without an app object. Rather than have a module
under lib/ reach for a global, it exposes install(app) and server.py — which owns the app —
hands it over. The janitor is symmetrical: start_janitor() / stop_janitor(), called from
server.py's startup and shutdown hooks, where the process lifecycle actually lives.
register_demo_janitor_hook IS PART OF THE PLUGIN CONTRACT. It is a key in plugin_context,
so plugins hold it as a LIVE REFERENCE from setup(). server.py imports this exact object
and puts it in the dict unchanged — identity preserved, and
tests/test_plugin_context_contract.py (#898, merged) fails if that ever stops being true.
This is the first carve that guard has actually protected.
━━━ stop_janitor()'s ORDER IS LOAD-BEARING ━━━
The obvious way to write it — clear the "started" flag, then join — is WRONG, and I wrote
it that way first. server.py's original deliberately returns EARLY, leaving
_DEMO_JANITOR_STARTED True and the thread handle intact, when the thread outlives the join:
# Leave _DEMO_JANITOR_STARTED True so a new janitor is not
# spawned by a subsequent startup while the old one is alive.
Clearing the flag first quietly reintroduces exactly the double-janitor leak the flag
exists to prevent. Preserved byte-for-byte, and the reason is now written down at the
function rather than only at its single call site.
━━━ A BUG MOVED VERBATIM, ON PURPOSE ━━━
if getenv_compat("FEEDBACK_DEMO_MODE") or getenv_compat("FEEDBACK_DEMO_MODE") == "1" \
and not _DEMO_JANITOR_STARTED:
`and` binds tighter than `or`, so this is `A or (B and C)` — the not-already-started
re-entry guard is DEAD whenever the env var is truthy, which is the only case that runs.
A second startup leaks a janitor thread (the handle is overwritten, so shutdown joins only
the last). Verified. Preserved exactly and filed as issue #902: a carve whose whole value
is being provably behaviour-neutral is not the place to change behaviour.
pyflakes caught three more missing imports on the way in (uuid, warnings x2). Five carves,
ten missing imports, every one a NameError on a live path.
pytest 2399, pyflakes 0, Codex 0.
Refs #48
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| routers | ||
| acoustid_match.py | ||
| appconfig.py | ||
| appstate.py | ||
| audio_effects_db.py | ||
| audio.py | ||
| builtin_content.py | ||
| demo_mode.py | ||
| diagnostics_bundle.py | ||
| diagnostics_hardware.py | ||
| diagnostics_redact.py | ||
| dlc_paths.py | ||
| drums.py | ||
| enrichment.py | ||
| env_compat.py | ||
| gp2midi.py | ||
| gp2notation.py | ||
| gp2rs_gpx.py | ||
| gp2rs.py | ||
| gp8_audio_sync.py | ||
| gp_autosync.py | ||
| jsonc.py | ||
| library_registry.py | ||
| logging_setup.py | ||
| loosefolder.py | ||
| lyrics_transcribe.py | ||
| mb_match.py | ||
| metadata_db.py | ||
| midi_import.py | ||
| notation_lift.py | ||
| notation.py | ||
| progression.py | ||
| reqfields.py | ||
| safepath.py | ||
| scan_worker.py | ||
| scan.py | ||
| sloppak.py | ||
| song_score.py | ||
| song.py | ||
| songmeta.py | ||
| tailwind_rebuild.py | ||
| tones.py | ||
| tunings.py | ||
| vocal_pitch.py | ||
| wem_decode.py | ||
| xp.py | ||