diff --git a/src/seedsigner/models/threads.py b/src/seedsigner/models/threads.py index 0fe5651c1..ddc066d94 100644 --- a/src/seedsigner/models/threads.py +++ b/src/seedsigner/models/threads.py @@ -7,6 +7,16 @@ class BaseThread(Thread): def __init__(self): super().__init__(daemon=True) + + # Intercept the subclass's run() method to catch silent thread crashes + orig_run = self.run + def wrapped_run(): + try: + orig_run() + except Exception as e: + logger.exception(f"Thread {self.__class__.__name__} crashed: {e}") + self.keep_running = False + self.run = wrapped_run def start(self): logger.debug(f"{self.__class__.__name__} STARTING") diff --git a/tests/test_threads.py b/tests/test_threads.py new file mode 100644 index 000000000..9e0be6ee6 --- /dev/null +++ b/tests/test_threads.py @@ -0,0 +1,21 @@ +import time +import logging +from seedsigner.models.threads import BaseThread + +def test_basethread_crash_handling(caplog): + + # This simulates a background process (like hardware listener or scroll ui) + # breaking unexpectedly. + class ExplodingThread(BaseThread): + def run(self): + raise ValueError("Intentional Crash!") + + with caplog.at_level(logging.ERROR): + t = ExplodingThread() + t.start() + + time.sleep(0.1) + + assert getattr(t, 'keep_running', True) is False + + assert "Thread ExplodingThread crashed: Intentional Crash!" in caplog.text