Describe the bug
When the deck wakes from the screensaver, most keys come back blank, or with the wrong image and no background — only the few key-render tasks queued before shutdown paint, and those without their backgrounds. The key actions still work (pressing a blank key fires its binding), so only the rendering is broken; the page config and input handlers loaded fine.
The render thread pool has already been shut down, but the long-lived MediaPlayerThread keeps trying to submit render tasks to it on resume, raising:
RuntimeError: cannot schedule new futures after interpreter shutdown
The entire traceback is in core src/backend/DeckManagement/DeckController.py — there are no plugin frames in it (full traceback below), so this looks like a core resume-path bug, not a plugin. Related but distinct from #535 (that one segfaults after load_background; here the app survives and just leaves the deck un-repainted).
To Reproduce
- Start StreamController (Flatpak) with a Stream Deck connected.
- Let the screensaver activate (idle for the configured delay).
- Wake the display / dismiss the screensaver.
- Deck comes back mostly blank; keys are functional but not drawn.
Intermittent — happens on some wakes, not all (appears timing-related, matching the note in #535).
Expected behavior
On resume, all keys repaint with their correct icons and backgrounds.
Screenshots
Before (deck blank / wrong colors after resume):
After (fix-streamcontroller restart — all keys repaint correctly):
Additional context
Full traceback:
ERROR | src.backend.DeckManagement.DeckController:run:78 - An error has been caught in function 'run', process 'MainProcess', thread 'MediaPlayerThread':
Traceback (most recent call last):
File "/usr/lib/python3.12/threading.py", line 1032, in _bootstrap
self._bootstrap_inner()
File "/usr/lib/python3.12/threading.py", line 1075, in _bootstrap_inner
self.run()
File "/app/bin/StreamController/src/backend/DeckManagement/DeckController.py", line 197, in run
self.perform_media_player_tasks()
File "/app/bin/StreamController/src/backend/DeckManagement/DeckController.py", line 308, in perform_media_player_tasks
task.run()
File "/app/bin/StreamController/src/backend/DeckManagement/DeckController.py", line 78, in run
self._callable(*self.args, **self.kwargs)
File "/app/bin/StreamController/src/backend/DeckManagement/DeckController.py", line 670, in load_all_inputs
futures.append(executor.submit(self.load_input, controller_input, page, update))
File "/usr/lib/python3.12/concurrent/futures/thread.py", line 173, in submit
raise RuntimeError('cannot schedule new futures after '
RuntimeError: cannot schedule new futures after interpreter shutdown
Preceding log context (crash fires right after "Hiding screen saver"):
ScreenSaver:hide:126 - Hiding screen saver
DeckController:load_page:737 - Loading page Main on deck CL46I1A01110
DeckController:load_brightness:638 - 50
DeckController:load_background:610 - Loading background in thread: ...
DeckController:load_screensaver:648 - Loading screensaver in thread: ...
DeckController:load_page:768 - Loaded page Main on deck CL46I1A01110
DeckController:run:78 - An error has been caught ... thread 'MediaPlayerThread' ...
Root cause (hypothesis): MediaPlayerThread (a persistent daemon thread) calls load_all_inputs() on resume, which does executor.submit(...) on a ThreadPoolExecutor that was shut down during the screensaver/teardown and never recreated. Either the executor should be recreated before the resume repaint, or load_all_inputs should guard against submitting to a dead pool (and the media-player loop should stop enqueuing after teardown).
Possible data-loss amplifier: when this cascades to a full app crash, data/pages/Main.json can get truncated to 0 bytes. An empty file is still valid JSON, so the invalid-JSON auto-fallback to pages/backups/Main.json does not trigger — the app then loads an empty page and the deck stays permanently dark until the page file is restored by hand. Suggests page saves aren't atomic (no temp-file + rename); worth hardening separately, as it turns a transient render glitch into a lost layout.
Environment:
- OS: CachyOS Linux (kernel 7.1.2-3-cachyos), Hyprland (Wayland)
- StreamController: 1.5.0-beta.14 (Flatpak, system install), commit cf05ced6b1902e1c98a6dd9087b17ad0484825f16c704416dfaf07f2d838a506
- Device: Elgato Stream Deck XL (USB 0fd9:006c)
- Python (Flatpak runtime): 3.12
Related issues: #535 (segfault variant of resume crash), #526 (stabilize resume during transient HID errors), #565 / #567 (resume/startup fixes in the same area), #604 (beta.14 startup instability, same version).
Describe the bug
When the deck wakes from the screensaver, most keys come back blank, or with the wrong image and no background — only the few key-render tasks queued before shutdown paint, and those without their backgrounds. The key actions still work (pressing a blank key fires its binding), so only the rendering is broken; the page config and input handlers loaded fine.
The render thread pool has already been shut down, but the long-lived MediaPlayerThread keeps trying to submit render tasks to it on resume, raising:
The entire traceback is in core src/backend/DeckManagement/DeckController.py — there are no plugin frames in it (full traceback below), so this looks like a core resume-path bug, not a plugin. Related but distinct from #535 (that one segfaults after load_background; here the app survives and just leaves the deck un-repainted).
To Reproduce
Intermittent — happens on some wakes, not all (appears timing-related, matching the note in #535).
Expected behavior
On resume, all keys repaint with their correct icons and backgrounds.
Screenshots
Before (deck blank / wrong colors after resume):
After (fix-streamcontroller restart — all keys repaint correctly):
Additional context
Full traceback:
ERROR | src.backend.DeckManagement.DeckController:run:78 - An error has been caught in function 'run', process 'MainProcess', thread 'MediaPlayerThread': Traceback (most recent call last): File "/usr/lib/python3.12/threading.py", line 1032, in _bootstrap self._bootstrap_inner() File "/usr/lib/python3.12/threading.py", line 1075, in _bootstrap_inner self.run() File "/app/bin/StreamController/src/backend/DeckManagement/DeckController.py", line 197, in run self.perform_media_player_tasks() File "/app/bin/StreamController/src/backend/DeckManagement/DeckController.py", line 308, in perform_media_player_tasks task.run() File "/app/bin/StreamController/src/backend/DeckManagement/DeckController.py", line 78, in run self._callable(*self.args, **self.kwargs) File "/app/bin/StreamController/src/backend/DeckManagement/DeckController.py", line 670, in load_all_inputs futures.append(executor.submit(self.load_input, controller_input, page, update)) File "/usr/lib/python3.12/concurrent/futures/thread.py", line 173, in submit raise RuntimeError('cannot schedule new futures after ' RuntimeError: cannot schedule new futures after interpreter shutdownPreceding log context (crash fires right after "Hiding screen saver"):
Root cause (hypothesis): MediaPlayerThread (a persistent daemon thread) calls load_all_inputs() on resume, which does executor.submit(...) on a ThreadPoolExecutor that was shut down during the screensaver/teardown and never recreated. Either the executor should be recreated before the resume repaint, or load_all_inputs should guard against submitting to a dead pool (and the media-player loop should stop enqueuing after teardown).
Possible data-loss amplifier: when this cascades to a full app crash, data/pages/Main.json can get truncated to 0 bytes. An empty file is still valid JSON, so the invalid-JSON auto-fallback to pages/backups/Main.json does not trigger — the app then loads an empty page and the deck stays permanently dark until the page file is restored by hand. Suggests page saves aren't atomic (no temp-file + rename); worth hardening separately, as it turns a transient render glitch into a lost layout.
Environment:
Related issues: #535 (segfault variant of resume crash), #526 (stabilize resume during transient HID errors), #565 / #567 (resume/startup fixes in the same area), #604 (beta.14 startup instability, same version).