Skip to content

Commit f1621b6

Browse files
author
jaseg
committed
Fix race condition in property observer code leading to futures.InvalidStateError
Previously, prepare_and_wait_for_property was slightly confused on the lifetime of that future. This closes #282
1 parent 5bb298a commit f1621b6

File tree

2 files changed

+40
-15
lines changed

2 files changed

+40
-15
lines changed

mpv.py

+19-14
Original file line numberDiff line numberDiff line change
@@ -1036,30 +1036,38 @@ def observer(name, val):
10361036
rv = cond(val)
10371037
if rv:
10381038
result.set_result(rv)
1039+
1040+
except InvalidStateError:
1041+
pass
1042+
10391043
except Exception as e:
10401044
try:
10411045
result.set_exception(e)
1042-
except InvalidStateError:
1046+
except:
10431047
pass
1044-
except InvalidStateError:
1045-
pass
1046-
self.observe_property(name, observer)
1047-
err_unregister = self._set_error_handler(result)
10481048

10491049
try:
10501050
result.set_running_or_notify_cancel()
1051+
1052+
self.observe_property(name, observer)
1053+
err_unregister = self._set_error_handler(result)
10511054
if catch_errors:
10521055
self._exception_futures.add(result)
10531056

10541057
yield result
10551058

1056-
rv = cond(getattr(self, name.replace('-', '_')))
1057-
if level_sensitive and rv:
1058-
result.set_result(rv)
1059+
if level_sensitive:
1060+
rv = cond(getattr(self, name.replace('-', '_')))
1061+
if rv:
1062+
result.set_result(rv)
1063+
return
1064+
1065+
self.check_core_alive()
1066+
result.result(timeout)
1067+
1068+
except InvalidStateError:
1069+
pass
10591070

1060-
else:
1061-
self.check_core_alive()
1062-
result.result(timeout)
10631071
finally:
10641072
err_unregister()
10651073
self.unobserve_property(name, observer)
@@ -1821,9 +1829,6 @@ def open_backend(_userdata, uri, cb_info):
18211829
pass
18221830
else:
18231831
warnings.warn(f'Unhandled exception {e} inside stream open callback for URI {uri}\n{traceback.format_exc()}')
1824-
1825-
1826-
18271832
return ErrorCode.LOADING_FAILED
18281833

18291834
cb_info.contents.cookie = None

tests/test_mpv.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import os.path
2424
import os
2525
import time
26-
from concurrent.futures import Future
26+
from concurrent.futures import Future, InvalidStateError
2727

2828
os.environ["PATH"] = os.path.dirname(__file__) + os.pathsep + os.environ["PATH"]
2929

@@ -915,6 +915,25 @@ def test_async_command(self):
915915

916916
class RegressionTests(MpvTestCase):
917917

918+
def test_wait_for_property_concurrency(self):
919+
players = [mpv.MPV(vo=testvo, loglevel='debug', log_handler=timed_print()) for i in range(2)]
920+
921+
try:
922+
for _ in range(150):
923+
for player in players:
924+
player.play('tests/test.webm')
925+
for player in players:
926+
player.wait_for_property('seekable')
927+
for player in players:
928+
player.seek(0, reference='absolute', precision='exact')
929+
930+
except InvalidStateError:
931+
self.fail('InvalidStateError thrown from wait_for_property')
932+
933+
finally:
934+
for player in players:
935+
player.terminate()
936+
918937
def test_unobserve_property_runtime_error(self):
919938
"""
920939
Ensure a `RuntimeError` is not thrown within
@@ -966,3 +985,4 @@ def t(self, *args, **kw):
966985
m.slang = 'ru'
967986
m.terminate() # needed for synchronization of event thread
968987
handler.assert_has_calls([mock.call('slang', ['jp']), mock.call('slang', ['ru'])])
988+

0 commit comments

Comments
 (0)