Problem
An exception raised by an mpv event callback escapes MPV._handle_event(). The remaining callbacks for that event are skipped, and the exception propagates out of _event_reader(), terminating the event-dispatch thread.
MpvManager.on_property_idle_active() also assumes aqt.mw is available. If an idle notification arrives after it has been cleared during teardown, the callback raises AttributeError.
Reproduction
This is a deterministic callback-level reproduction, not a report of a normal-review playback failure. In an Anki development Python environment with aqt importable, run:
from aqt.mpv import MPV
player = MPV.__new__(MPV)
player._callbacks_initialized = True
calls = []
def failing_callback():
raise RuntimeError("callback failed")
player._callbacks = {
"file-loaded": [failing_callback, lambda: calls.append("handled")]
}
player._handle_event({"event": "file-loaded"})
assert calls == ["handled"]
Before the fix, the call raises RuntimeError and the second callback is not invoked. Property-change callbacks have the same problem.
The teardown case can be reproduced in an isolated test by creating an uninitialized MpvManager, assigning a completion callback, monkeypatching aqt.mw to None, and calling on_property_idle_active(True). Before the fix, this raises AttributeError: 'NoneType' object has no attribute 'taskman'.
Expected behavior
- A failing callback is reported without preventing other callbacks or later events from being processed.
- An idle notification received after the main window has been cleared does not try to schedule work through it.
- With a live main window, completion still runs through
taskman.run_on_main().
Impact and evidence
If a callback raises during event dispatch, subsequent mpv completion/property notifications are no longer handled by that event thread. This could leave playback bookkeeping or a queued playback sequence stuck. That downstream playback consequence has not been reproduced during normal review; the exception paths are covered by deterministic regression tests. No data loss is claimed.
Relevant code at the tested baseline:
Tested against main at 5edc31694 on macOS with Python 3.13.13. A small fix with regression tests is ready.
Problem
An exception raised by an mpv event callback escapes
MPV._handle_event(). The remaining callbacks for that event are skipped, and the exception propagates out of_event_reader(), terminating the event-dispatch thread.MpvManager.on_property_idle_active()also assumesaqt.mwis available. If an idle notification arrives after it has been cleared during teardown, the callback raisesAttributeError.Reproduction
This is a deterministic callback-level reproduction, not a report of a normal-review playback failure. In an Anki development Python environment with
aqtimportable, run:Before the fix, the call raises
RuntimeErrorand the second callback is not invoked. Property-change callbacks have the same problem.The teardown case can be reproduced in an isolated test by creating an uninitialized
MpvManager, assigning a completion callback, monkeypatchingaqt.mwtoNone, and callingon_property_idle_active(True). Before the fix, this raisesAttributeError: 'NoneType' object has no attribute 'taskman'.Expected behavior
taskman.run_on_main().Impact and evidence
If a callback raises during event dispatch, subsequent mpv completion/property notifications are no longer handled by that event thread. This could leave playback bookkeeping or a queued playback sequence stuck. That downstream playback consequence has not been reproduced during normal review; the exception paths are covered by deterministic regression tests. No data loss is claimed.
Relevant code at the tested baseline:
Tested against
mainat5edc31694on macOS with Python 3.13.13. A small fix with regression tests is ready.