Repository navigation
Conversation
nazbert
added a commit
to nazbert/Deckard
that referenced
this pull request
Jul 12, 2026
Screensaver.update_image() reloaded the screensaver against deck_controller.active_page, but load_screensaver() dereferences page.dict -- a None active_page (right after connect, or with zero pages) would raise. Same class as the Brightness/Screensaver None-guards already landed in this file; guard the call site consistently. Also close the item StreamController#8 test gap: the regression scenario only covered deliver_selection, but the fix patched both delivery paths. Add a check that CustomAssets/FlowBox.on_child_activated captures-then-nulls the manager's callback refs before spawning the delivery thread. Annotate BackgroundGroup.load_defaults_from_page as dead (its bare early return disables the unguarded active_page.dict derefs below it).
nazbert
added a commit
to nazbert/Deckard
that referenced
this pull request
Jul 12, 2026
Completes the item-StreamController#1 pattern (issue StreamController#55) in the sibling method: the loop re-read controller.active_page per check, so a non-None guard could race a concurrent (dis)connect clearing it and then deref None.json_path. Snapshot it once, mirroring update_input/update_key_image (item StreamController#8). _pages_lock is the wrong tool here -- it guards self.pages, not active_page writes. Also document that _save_locks grows one entry per page path and is never pruned (bounded by page count; no eviction machinery warranted). Extends scenario_reload_similar_pages with a flip-to-None probe (red-checked: pre-fix raises AttributeError on .json_path).
nazbert
added a commit
to nazbert/Deckard
that referenced
this pull request
Jul 12, 2026
…longer freezes the deck (StreamController#1) The writer thread (sole path for paints/brightness/Clear/ClearAndClose since the single-writer migration) ran unguarded: any uncaught exception killed it, freezing the deck until replug while control_q grew unbounded. run() now delegates to _run_one_tick() inside a guarded loop: a tick exception is logged with full traceback (rate-limited to 1/5s with a suppressed-repeat count -- local limiter until StreamController#91), _stop is honored in the except path so a failing body can't strand close(), and a 0.25s wake-event backoff bounds a persistent failure at ~4 retries/s instead of a 100% spin. @log.catch on run() would log once and let the thread die -- the guard must live inside the while. Composes with StreamController#80: the hooks report an escaping death; this prevents the death. The five audit-verified crash vectors are also fixed at the source: (a) init_inputs is build-then-swap and the screensaver drops its `inputs = {}` pre-clear (was an empty-dict window on every screensaver entry); the loop snapshots the dict once per tick and uses .get(); (b) the loop snapshots background.video once (set_video(None) race); (c) touchscreen get_current_image guards a nulled active_page (blank strip); (d) the touchscreen tick gate snapshots state.background_video (_release_background_video race); (e) _exec_clear snapshots the touchscreen slot and nulls it identity-compared -- the crash half of StreamController#1 and this site's half of StreamController#8. tests/scenario_writer_survival.py (first leg of StreamController#61) runs the REAL loop over the stub tier: survives a poisoned tick, resumes paints, caps the log storm, and stop() joins while every tick raises. fixtures gains _QuietInputState (get_active_state) since the live loop path never executed on stubs before (StreamController#69 stub-drift). Suite: 34/34.
nazbert
added a commit
to nazbert/Deckard
that referenced
this pull request
Jul 12, 2026
Issue StreamController#8 / B-08, both shapes plus two adjacent ones, all under a new MediaPlayerThread._slot_lock (uncontended acquire once per tick): - Drain half: perform_media_player_tasks read the touchscreen slot then unconditionally nulled it. A producer assigning between the two lost its frame -- and with _last_enqueued_hash already stamped at enqueue, STATIC content never re-enqueued: the strip stayed stale until the next content change. The read+null is now atomic under the lock. - Clear half: _exec_clear's per-key get-then-del could delete a NEWER image task whose submit_seq contractually survives the Clear (the touchscreen side of this had already been identity-narrowed; both are now fully closed under the lock). - Write-cap putback: the latest-wins re-queue checked the slot for None without the lock -- a producer's newer frame landing between check and putback was clobbered by the older deferred frame. - clear_media_player_tasks / _exec_clear_and_close slot wipes join the same critical section. Lock ordering: _page_gen_lock -> _slot_lock in one place, never reversed. tests/scenario_touchscreen_slot_race.py makes both interleaves deterministic (hooked-property read triggers a real producer inside the window) and red-checks them: pre-fix both frames are lost.
nazbert
added a commit
to nazbert/Deckard
that referenced
this pull request
Jul 12, 2026
…treamController#8 review round 1) Adds two checks to scenario_touchscreen_slot_race.py, closing the review gap on the sites most entangled with the merged !27 write-cap: - check_writecap_putback: the !27 rate-cap re-queues an over-budget touchscreen frame into the single slot iff still None. The None-check-then-set is now under _slot_lock; the test forces a producer to assign a NEWER frame into the check->set window (via a hook on the putback's own 2nd slot read) and asserts it survives -- AND that the deferred over-budget frame is NOT written to the device, so the !27 rate-limit is preserved (no write-flood). Red-checked: with only the putback lock reverted (all other locks intact), FAIL(3) fires -- the newer frame is clobbered by the older deferred frame. - check_slot_wipes: clear_media_player_tasks (the one nested _page_gen_lock -> _slot_lock ordering) and _exec_clear_and_close both leave a coherent, wiped slot with no lock inversion. hook_types gains a fire-on-Nth-read mode so a test can target the putback read without firing on the drain's earlier read. Also documents (NIT) that the has_pending slot reads in _run_one_tick are intentionally unlocked -- a torn read only mis-picks target FPS for one tick and self-corrects. The producer-vs-producer submit-seq inversion (last-assigner-wins, pre-existing, out of StreamController#8's scope) is tracked in StreamController#130.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.