espeak: do not call espeak_Cancel() while holding queue_guard

When a flush is requested, the espeak thread called stop_speech() ->
espeak_Cancel() with queue_guard held.  espeak_Cancel() waits for
espeak-ng's internal say thread to acknowledge the cancellation, and
that thread can be blocked indefinitely inside a blocking ALSA call
(snd_pcm_writei/snd_pcm_drain on a wedged device, as seen with EBUSY
errors).  In that case queue_guard was held forever, which in turn:

- blocked the signal thread on pthread_mutex_lock(), so SIGINT/SIGTERM
  appeared to be ignored and only SIGKILL could end the process;
- left the softsynth thread stuck in request_espeak_stop(), so
  /dev/softsynth was no longer drained, speakup's kernel buffer filled
  up, and console output (e.g. dmesg) stalled.

Release queue_guard around the espeak_Cancel() call.  This is safe
because the only queue producer, the softsynth thread, is blocked
waiting for stop_acknowledged for as long as stop_requested is set, so
the queue cannot be mutated concurrently.

Helps: https://github.com/linux-speakup/espeakup/issues/45
Helps: https://github.com/linux-speakup/espeakup/issues/62

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Alexander Epaneshnikov 2026-06-11 11:05:10 +03:00 committed by Samuel Thibault
commit d1f7e2384b

View file

@ -492,7 +492,16 @@ void *espeak_thread(void *arg)
if (stop_requested) {
current = NULL;
/* Call into espeak with queue_guard released: espeak_Cancel
* can take time, or even block indefinitely when the audio
* output is wedged, and holding the lock here would prevent
* the other threads from ever making progress again. The
* queue cannot change concurrently: the only producer (the
* softsynth thread) is blocked waiting for stop_acknowledged
* as long as stop_requested is set. */
pthread_mutex_unlock(&queue_guard);
stop_speech();
pthread_mutex_lock(&queue_guard);
synth_queue_clear();
stop_requested = 0;
pthread_cond_signal(&stop_acknowledged);