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._terminate_flag = False
self._process: Optional[subprocess.Popen] = None
self._lock = threading.Lock()
def play(self, tag: AVTag, on_done: OnDoneCallback) -> None:
self._taskman.run_in_background(
@ -239,7 +240,7 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method
self._terminate_flag = True
# block until stopped
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)
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)
)
try:
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:
self._process.wait(0.1)
if self._process.returncode != 0:
print(f"player got return code: {self._process.returncode}")
self._process = None
self._terminate_flag = False
return
except subprocess.TimeoutExpired:
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:
try: