possible fix for race conditions in the sound code

https://anki.tenderapp.com/discussions/ankidesktop/39030-erro-ao-adicionar-arquivo-de-udio

the lock should at least ensure _process doesn't disappear in the
middle of our logic, and the longer wait should reduce the chances
of .stop() timing out and allowing multiple audio files to play

Not very happy with the current approach, as in the timeout case
you have multiple threads competing to access the same data
This commit is contained in:
Damien Elmes 2020-02-21 15:14:09 +10:00
parent abe9f50c14
commit 598226a5c0

View file

@ -229,6 +229,7 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method
self._taskman = taskman self._taskman = taskman
self._terminate_flag = False self._terminate_flag = False
self._process: Optional[subprocess.Popen] = None self._process: Optional[subprocess.Popen] = None
self._lock = threading.Lock()
def play(self, tag: AVTag, on_done: OnDoneCallback) -> None: def play(self, tag: AVTag, on_done: OnDoneCallback) -> None:
self._taskman.run_in_background( self._taskman.run_in_background(
@ -239,7 +240,7 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method
self._terminate_flag = True self._terminate_flag = True
# block until stopped # block until stopped
t = time.time() t = time.time()
while self._terminate_flag and time.time() - t < 1: while self._terminate_flag and time.time() - t < 3:
time.sleep(0.1) time.sleep(0.1)
def _play(self, tag: AVTag) -> None: def _play(self, tag: AVTag) -> None:
@ -254,21 +255,32 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method
lambda: gui_hooks.av_player_did_begin_playing(self, tag) lambda: gui_hooks.av_player_did_begin_playing(self, tag)
) )
try:
while True: while True:
with self._lock:
# if .stop() timed out, another thread may run when
# there is no process
if not self._process:
self._process = None
self._terminate_flag = False
return
# should we abort playing?
if self._terminate_flag:
self._process.terminate()
self._process = None
self._terminate_flag = False
raise PlayerInterrupted()
# wait for completion
try: try:
self._process.wait(0.1) self._process.wait(0.1)
if self._process.returncode != 0: if self._process.returncode != 0:
print(f"player got return code: {self._process.returncode}") print(f"player got return code: {self._process.returncode}")
self._process = None
self._terminate_flag = False
return return
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
pass pass
if self._terminate_flag:
self._process.terminate()
raise PlayerInterrupted()
finally:
self._process = None
self._terminate_flag = False
def _on_done(self, ret: Future, cb: OnDoneCallback) -> None: def _on_done(self, ret: Future, cb: OnDoneCallback) -> None:
try: try: