Skip to content

Commit 208d8d6

Browse files
authored
Fix concurrent modification (#986)
Snapshot collections with .toList() / List.of() before iterating, preventing modification by concurrent async event handlers. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Fixed potential concurrent modification exceptions that could occur during async operations when iterating over participant data, event listeners, and audio elements. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent fa7db38 commit 208d8d6

File tree

6 files changed

+17
-13
lines changed

6 files changed

+17
-13
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
patch type="fixed" "Fix concurrent modification on collection iteration during async operations"

lib/src/core/room.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -539,8 +539,8 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
539539
// re-publish all tracks
540540
await localParticipant?.rePublishAllTracks();
541541

542-
for (var participant in _remoteParticipants) {
543-
for (var pub in participant.trackPublications.values) {
542+
for (var participant in _remoteParticipants.toList()) {
543+
for (var pub in participant.trackPublications.values.toList()) {
544544
if (pub.subscribed) {
545545
pub.sendUpdateTrackSettings();
546546
}
@@ -936,8 +936,8 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
936936
final trackSids = <String>[];
937937
final trackSidsDisabled = <String>[];
938938

939-
for (var participant in _remoteParticipants) {
940-
for (var track in participant.trackPublications.values) {
939+
for (var participant in _remoteParticipants.toList()) {
940+
for (var track in participant.trackPublications.values.toList()) {
941941
if (track.subscribed != autoSubscribe) {
942942
trackSids.add(track.sid);
943943
}
@@ -1084,7 +1084,7 @@ extension RoomHardwareManagementMethods on Room {
10841084
/// Set audio output device.
10851085
Future<void> setAudioOutputDevice(MediaDevice device) async {
10861086
if (lkPlatformIs(PlatformType.web)) {
1087-
for (final participant in _remoteParticipants) {
1087+
for (final participant in _remoteParticipants.toList()) {
10881088
for (final audioTrack in participant.audioTrackPublications) {
10891089
audioTrack.track?.setSinkId(device.deviceId);
10901090
}

lib/src/core/signal_client.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -517,10 +517,11 @@ extension SignalClientInternalMethods on SignalClient {
517517
// queue is empty
518518
if (_queue.isEmpty) return;
519519
// send requests
520-
for (final request in _queue) {
520+
final queueCopy = List.of(_queue);
521+
_queue.clear();
522+
for (final request in queueCopy) {
521523
_sendRequest(request, enqueueIfReconnecting: false);
522524
}
523-
_queue.clear();
524525
}
525526

526527
@internal

lib/src/e2ee/e2ee_manager.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ class E2EEManager {
141141
await _listener?.cancelAll();
142142
await _listener?.dispose();
143143
_listener = null;
144-
for (var frameCryptor in _frameCryptors.values) {
144+
for (var frameCryptor in _frameCryptors.values.toList()) {
145145
await frameCryptor.dispose();
146146
}
147147
_frameCryptors.clear();
@@ -178,7 +178,7 @@ class E2EEManager {
178178
/// without encryption/decryption
179179
Future<void> setEnabled(bool enabled) async {
180180
_enabled = enabled;
181-
for (var frameCryptor in _frameCryptors.entries) {
181+
for (var frameCryptor in _frameCryptors.entries.toList()) {
182182
await frameCryptor.value.setEnabled(enabled);
183183
}
184184
}
@@ -189,7 +189,7 @@ class E2EEManager {
189189
/// if null, use local participant.
190190
Future<void> setKeyIndex(int keyIndex, {String? participantIdentity}) async {
191191
participantIdentity ??= _room?.localParticipant?.identity;
192-
for (var item in _frameCryptors.entries) {
192+
for (var item in _frameCryptors.entries.toList()) {
193193
if (item.key.keys.first == participantIdentity) {
194194
await item.value.setKeyIndex(keyIndex);
195195
}
@@ -202,7 +202,7 @@ class E2EEManager {
202202
/// @return the key index and -1 if not found
203203
Future<int> getKeyIndex(String? participantIdentity) async {
204204
participantIdentity ??= _room?.localParticipant?.identity;
205-
for (var item in _frameCryptors.entries) {
205+
for (var item in _frameCryptors.entries.toList()) {
206206
if (item.key.keys.first == participantIdentity) {
207207
return await item.value.keyIndex;
208208
}

lib/src/managers/event.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ abstract class EventsListenable<T> extends Disposable {
133133
if (_listeners.isNotEmpty) {
134134
// Stop listening to all events
135135
logger.finer('${objectId} cancelling ${_listeners.length} listeners(s)');
136-
for (final listener in _listeners) {
136+
final listenersCopy = List.of(_listeners);
137+
_listeners.clear();
138+
for (final listener in listenersCopy) {
137139
await listener.cancel();
138140
}
139141
}

lib/src/track/web/_audio_html.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Future<dynamic> startAudio(String id, rtc.MediaStreamTrack track) async {
5252
}
5353

5454
Future<bool> startAllAudioElement() async {
55-
for (final el in _audioElements.values) {
55+
for (final el in _audioElements.values.toList()) {
5656
if (el.instanceOfString('HTMLAudioElement')) {
5757
final audio = el as web.HTMLAudioElement;
5858
await audio.play().toDart;

0 commit comments

Comments
 (0)