fix(android): avoid holding initLock while calling into TextToSpeech to prevent ABBA deadlock#12
Open
RemiHin wants to merge 1 commit into
Open
fix(android): avoid holding initLock while calling into TextToSpeech to prevent ABBA deadlock#12RemiHin wants to merge 1 commit into
RemiHin wants to merge 1 commit into
Conversation
…to prevent ABBA deadlock
ninjz
added a commit
to ninjz/react-native-speech
that referenced
this pull request
Jun 30, 2026
… OOM) Two crashes on low-end Android share one root cause: the onInit SUCCESS branch enumerates voices/engines synchronously on the main thread. Android delivers the TextToSpeech OnInitListener on the main thread (SetupConnectionAsyncTask.onPostExecute). The SUCCESS branch then ran synthesizer.engines, applyGlobalOptions() (-> synthesizer.voices) and processPendingOperations() (which drains queued getAvailableVoices / getEngines / configure) on that thread -- all Binder IPC round-trips to the engine process. ANR: on a cold engine under memory pressure these take seconds; back-to-back on the UI thread they exceed the 5s ANR threshold (observed on a 4GB Android 15 device). This is distinct from the mhpdev-com#11 deadlock -- the main thread is executing synthesizer.voices, not waiting on initLock. OOM crash: setLanguage()/voices hit TextToSpeech.getVoices(), and some cheap engines (seen on MediaTek/UMIDIGI Android 12) return a pathological serialized Voice[] that OutOfMemoryErrors while deserializing -- a runaway allocation, not memory pressure (seen with ~2.9GB free). The existing catch (e: Exception) does not catch it (OOM is an Error, not an Exception), so it escaped uncaught and crashed the app. Fixes: - Run the engine/voice enumeration, global config and queue drain off the main thread. Apply config before flipping isInitialized so callers can't speak before config is applied. initLock guards only the flag flip; applyGlobalOptions() and the drain run lock-free, since holding initLock across a TextToSpeech call is an ABBA deadlock (see mhpdev-com#11). cachedEngines becomes @volatile. - Widen catch (e: Exception) -> catch (e: Throwable) in applyGlobalOptions() and applyOptions() so a getVoices() OOM degrades instead of crashing, and backstop the background thread with try/catch (Throwable). - Resolve synthesizer.defaultEngine once in getEngines() instead of once per engine inside the loop. Complements mhpdev-com#11 / mhpdev-com#12: keeps initLock off the TextToSpeech call path, so it does not reintroduce that deadlock. Rebase onto mhpdev-com#12 if it lands first.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #11