Skip to content

fix(ios): add thread-safety to HybridVideoPlayerEventEmitter listeners#4860

Open
erank-pixel wants to merge 1 commit into
TheWidlarzGroup:masterfrom
erank-pixel:fix/event-emitter-thread-safety
Open

fix(ios): add thread-safety to HybridVideoPlayerEventEmitter listeners#4860
erank-pixel wants to merge 1 commit into
TheWidlarzGroup:masterfrom
erank-pixel:fix/event-emitter-thread-safety

Conversation

@erank-pixel
Copy link
Copy Markdown
Contributor

@erank-pixel erank-pixel commented Mar 17, 2026

Summary

HybridVideoPlayerEventEmitter.listeners is accessed from multiple threads without synchronization:

  • Event emission (emitEvent) happens from Swift concurrency async contexts — e.g. SourceLoaderActor.load()HybridVideoPlayer.initializePlayerItem()onLoadStart() runs on a cooperative thread pool queue.
  • Listener add/remove happens from the JS thread via addListener / subscription remove closures.

This causes EXC_BAD_ACCESS (SIGSEGV) crashes in _swift_release_dealloc when the array is mutated during iteration, as Swift's copy-on-write optimization leaves a dangling buffer pointer.

Crash stack (abbreviated)

Thread 4 (crashed):
  libswiftCore.dylib  _swift_release_dealloc
  ReactNativeVideo    HybridVideoPlayerEventEmitter.emitEvent<τ_0_0>(…)
  ReactNativeVideo    HybridVideoPlayerEventEmitter.onLoadStart(…)
  ReactNativeVideo    HybridVideoPlayer.initializePlayerItem(…)
  ReactNativeVideo    closure #1 in HybridVideoPlayer.loadSource(…)

Fix

  • Protect all reads/writes to the listeners array with an NSLock
  • Snapshot the array before iterating in emitEvent so callbacks execute outside the lock (avoids deadlocks, keeps the critical section minimal)
  • Make the backing store private to prevent unprotected external access

Test plan

  • Reproduced the crash in a production app with rapid video source changes
  • Applied the fix via patch-package — crash no longer reproduces
  • Verified event delivery still works correctly (onLoadStart, onLoad, onProgress, onEnd all fire)

The `listeners` array in `HybridVideoPlayerEventEmitter` is accessed
from multiple threads without synchronization. Events are emitted from
async Swift concurrency contexts (e.g. SourceLoaderActor → HybridVideoPlayer
→ emitEvent) while listeners may be added or removed from the JS thread,
causing EXC_BAD_ACCESS (SIGSEGV) crashes due to concurrent mutation
during iteration.

This commit:
- Protects all reads and writes to `_listeners` with an NSLock
- Snapshots the listener array before iterating in `emitEvent` so
  callbacks execute outside the lock (avoiding deadlocks and keeping
  the critical section minimal)
- Makes the backing store `private` to prevent unprotected external access

Made-with: Cursor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: To Triage

Development

Successfully merging this pull request may close these issues.

1 participant