Skip to content

fix: ConcurrentModificationException in getAudioDeviceStatusMap#260

Open
qiixiao wants to merge 2 commits into
react-native-webrtc:masterfrom
qiixiao:master
Open

fix: ConcurrentModificationException in getAudioDeviceStatusMap#260
qiixiao wants to merge 2 commits into
react-native-webrtc:masterfrom
qiixiao:master

Conversation

@qiixiao

@qiixiao qiixiao commented May 15, 2026

Copy link
Copy Markdown

Fix ConcurrentModificationException caused by iterating audioDevices while it's being modified on another thread.

Copy audioDevices to a new ArrayList before iterating to avoid concurrent modification.

Fix ConcurrentModificationException caused by iterating audioDevices 
while it's being modified on another thread.

Copy audioDevices to a new ArrayList before iterating to avoid 
concurrent modification.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to prevent a ConcurrentModificationException when building the audio-device status payload (getAudioDeviceStatusMap) by iterating over a snapshot of audioDevices rather than the live set.

Changes:

  • Iterate over a copied collection of audioDevices when building availableAudioDeviceList.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

WritableMap data = Arguments.createMap();
String audioDevicesJson = "[";
for (AudioDevice s: audioDevices) {
for (AudioDevice s: new ArrayList<>(audioDevices)) {
WritableMap data = Arguments.createMap();
String audioDevicesJson = "[";
for (AudioDevice s: audioDevices) {
for (AudioDevice s: new ArrayList<>(audioDevices)) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review — both points are valid.

  1. Compile error: right, java.util.ArrayList was missing. Fixed by adding the import.
  2. Snapshot doesn't fully prevent CME: agreed. The root cause is that audioDevices is a plain HashSet mutated from multiple threads —start() / chooseAudioRoute() run on the RN bridge thread while updateAudioDeviceState() runs on the UI thread, so even constructing the snapshot iterates a concurrently-modified set.

To fix it properly (and keep the change minimal), I switched audioDevices from HashSet to ConcurrentHashMap.newKeySet(). Iteration / snapshot construction is then weakly consistent and never throws CME. Since the field was previously reassigned (audioDevices = newAudioDevices), I changed that to clear() + addAll() so the concurrent set is updated in place rather than swapped out from under concurrent readers.

The new ArrayList<>(audioDevices) snapshot is retained so the JSON payload reflects a single point-in-time view rather than a live set.

switch audioDevices to a concurrent set and update in place
@qiixiao

qiixiao commented Jul 1, 2026

Copy link
Copy Markdown
Author

switch audioDevices to a concurrent set and update in place

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