request_espeak_stop() waited forever for the espeak thread to
acknowledge the stop. If espeak-ng is wedged inside the audio output
(e.g. an ALSA device blocked or stuck returning EBUSY, as reported in
issue #62), the acknowledgement never comes, and the softsynth thread
stops draining /dev/softsynth forever. Speakup's kernel buffer then
fills up and console output stalls, which matches the "blocks dmesg
output after a couple of pages" observation in issue #45. The process
goes silent and only SIGKILL gets rid of it.
Wait at most 10 seconds for the acknowledgement (a normal cancellation
takes milliseconds; the timeout can only trigger when espeak is truly
stuck). On timeout, exit with a clear message so that the init system
respawns espeakup in a clean state: our systemd unit already has
Restart=always. A one-second restart beats an unkillable silent
daemon, and was explicitly requested by the reporter of issue #62.
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>
On SIGINT/SIGTERM, the signal thread only set should_run to 0 and
relied on a wake-up chain to propagate the shutdown: the self-pipe
wakes the softsynth thread out of select(), which on exit signals
runner_awake to wake the espeak thread.
That chain breaks whenever the softsynth thread is not sitting in
select() but waiting on stop_acknowledged in request_espeak_stop():
nobody ever signals that condition variable on shutdown, so the thread
never re-evaluates should_run and the process never exits.
Broadcast all three condition variables after clearing should_run, so
that every parked thread re-checks its predicate, whichever wait it is
blocked in.
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>
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>
When espeak returns EE_BUFFER_FULL we should just wait a bit before
retrying. Also, we don't want to lose the current entry. We however want
to wake up as soon as possible on stop request.
This permits --pid-file to be specified.
The getopt_long option was looking for a lowercase p,
and the pid-file option used an uppercase P.
This meant that pid-file was never settable.
This allows to make sure to reach the maximum volume permitted by the
hardware. This makes the default volume (5) set ALSA volume to 80%, like
the default ALSA scripts do.
This is not enabled by default, since users will probably want to use a
mixer to fine-tune their volume. But for e.g. installation images, this
allows to spare the use of a mixer and just use the speakup volume
control.
When the Linux console is e.g. switched to a graphical VT, the kernel
emits \x01P to notify that it is not able to read the screen any more,
and the software synthesis can thus release the audio card, for other
screen readers to take over.
This implements it by adding a paused_espeak variable that tracks
whether we have suspended espeak. When more text comes, we can simply
reinitialize espeak.
This fixes#10.