Skip to content

fix: resolve Android stop() deadlock (self-join + EOS-never-queued race) - #508

Open
iawaisrana wants to merge 3 commits into
SimformSolutionsPvtLtd:mainfrom
iawaisrana:fix/android-stop-deadlock
Open

fix: resolve Android stop() deadlock (self-join + EOS-never-queued race)#508
iawaisrana wants to merge 3 commits into
SimformSolutionsPvtLtd:mainfrom
iawaisrana:fix/android-stop-deadlock

Conversation

@iawaisrana

Copy link
Copy Markdown

What

Fixes the Android stop() hang reported in #470, combining two changes:

  1. Cherry-picked from fix: Android recorderController.stop() never completes #486: removes the self-join deadlock in CommonEncoder.stopEncoder() (it called handlerThread.join() from a callback running on that same thread).
  2. New: a bounded fallback for a second, related race that fix: Android recorderController.stop() never completes #486 alone doesn't close — if signalToStop() runs while no input buffer is currently available, it waits for a future onInputBufferAvailable callback to queue EOS. Since audioRecord.stop() has already run by that point, that callback can simply never arrive on some devices, hanging forever even with fix: Android recorderController.stop() never completes #486 applied. This PR adds a grace-period fallback: if EOS isn't queued naturally in time, stopEncoder() is forced directly (it's already idempotent and always invokes the completion callback).

Why configurable

How long the natural path takes likely varies by device/OEM Codec2 implementation, so the grace period is exposed as AndroidEncoderSettings.stopTimeoutMs (default 500ms, matching the previous hardcoded value) rather than hardcoded.

Testing

Reproduced and verified via logcat on a physical device: without a fix, stop() on a second/third recording in a session would hang indefinitely with no further MediaCodec/CommonEncoder log output at all. With this fix, the fallback fires and logs a warning, then completes the stop within the grace period.

I didn't add automated tests — the repo has no existing test infrastructure for either the native Android side or the Dart side, and the actual bug is a device/OEM-dependent async timing race that isn't practical to reproduce deterministically in a unit test. Happy to add coverage if there's a preferred approach for this.

stopEncoder() ran on its own handlerThread and called handlerThread.join()
on itself, deadlocking forever and preventing the completion callback
from firing. RecorderController.stop() would then hang indefinitely on
Android whenever the AAC/CommonEncoder path was used.

Removes the self-join, invokes the completion callback before quitting
the handler thread instead of after, posts MediaCodec callbacks and the
method channel result onto explicit handlers for thread-safety, and adds
@volatile to fields read across threads.

Cherry-picked from upstream PR SimformSolutionsPvtLtd#486.
signalToStop() relies on a future onInputBufferAvailable callback to
queue EOS when no input buffer is available at the moment stop() is
called. But recording has already stopped feeding new audio by then,
so that callback may never arrive, leaving stopEncoder() (and the
completion callback / Dart-side stop() future) hanging indefinitely.
Reproduced consistently on a second back-to-back recording.

Add a 500ms grace-period fallback: if EOS hasn't been queued naturally
by then, force stopEncoder() directly. It's idempotent and always
invokes the completion callback, so the Dart Future can never hang.
Different devices/Codec2 implementations may need more or less time
before the natural EOS path can be considered stalled. Hardcoding 500ms
risked either forcing a stop too early on slower devices (truncating
the recording's tail) or not being adjustable for apps that hit this
more severely.

Adds AndroidEncoderSettings.stopTimeoutMs (default 500, matching the
previous hardcoded value), threaded through to RecorderSettings on both
the Dart and native side, down to CommonEncoder.signalToStop()'s
postDelayed fallback.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants