Skip to content

Update to version 1.1.2-alpha - #8

Merged
Core447 merged 183 commits into
mainfrom
dev
Mar 24, 2024
Merged

Core447 merged 183 commits into
mainfrom
dev

Conversation

@Core447

@Core447 Core447 commented Mar 24, 2024

Copy link
Copy Markdown
Member

No description provided.

Core447 added 30 commits March 7, 2024 17:08
@Core447
Core447 merged commit 985da8f into main Mar 24, 2024
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant