jaseg / python-mpv

Python interface to the awesome mpv media player
https://git.jaseg.de/python-mpv.git
Other
531 stars 67 forks source link

Where should we raise exceptions from stream protocol handlers? #245

Closed riaqn closed 1 year ago

riaqn commented 1 year ago
import mpv

def open_fn(url):
    print('hello')
    raise ValueError

player = mpv.MPV()
player.register_stream_protocol("test", open_fn)
player.play('test://foo')
player.wait_for_playback()

This just print hello and quits peacefully. I would expect play to raise some exception like LoadFailure

jaseg commented 1 year ago

This is a bit of a tricky one. When registering the stream protocol, we throw libmpv a bunch of dynamically generated ctypes function pointers, that libmpv then calls as it pleases to process the stream. This means that when open_fn in your testcase gets called, it's call stack points straight into libmp, and will not even be on the same thread as wait_for_playback.

We could try to stash away the exception when it happens in open_fn, and then raise it in wait_for_playback, but that might be counter-intuitive given that the exception is ultimately caused by play, but would only be raised later in wait_for_playback. We can't wait for the exception in play since that is just a wrapper around mpv's loadfile command, which is async by nature.

I can't really think of a good semantic where to raise these exceptions. I'm interested to hear if you have any suggestions. In the meantime, my recommendation would be to use local try/except blocks inside the stream protocol handler functions.

riaqn commented 1 year ago

Storing the exception and rasing it in wait_for_playback sounds like a good idea. Async exception is not uncommon and, as terrible as it is, still better than silently discarding exceptions. The latter could really cause a debug hell for users.

jaseg commented 1 year ago

True, swallowing it is definitely worse. I'm still unsure about wait_for_playback though. By design, multiple threads might be waiting inside wait_for_playback at the same time. In that case, which one should raise the exception? For sure it shouldn't pop up in several of them, but choosing one at random also seems bad. I can see two options here:

I'm kinda partial towards the first option.

jaseg commented 1 year ago

I just pushed a draft of the first option. All of the wait_for_x functions now have a catch_errors arg that is True by default that enables them catching exceptions from both the event loop and stream callbacks. When an exception is raised while nothing is catching it, a warning with a stacktrace is printed. Please try it out, and tell me what you think.

jaseg commented 1 year ago

This is now out in v1.0.3. Feel free to open another issue if you hit any problems with it.