diff --git a/.changes/fix-concurrent-modification b/.changes/fix-concurrent-modification new file mode 100644 index 00000000..30ea6ede --- /dev/null +++ b/.changes/fix-concurrent-modification @@ -0,0 +1 @@ +patch type="fixed" "Fix concurrent modification on collection iteration during async operations" diff --git a/lib/src/core/room.dart b/lib/src/core/room.dart index 77ede0fb..3fbf92a4 100644 --- a/lib/src/core/room.dart +++ b/lib/src/core/room.dart @@ -539,8 +539,8 @@ class Room extends DisposableChangeNotifier with EventsEmittable { // re-publish all tracks await localParticipant?.rePublishAllTracks(); - for (var participant in _remoteParticipants) { - for (var pub in participant.trackPublications.values) { + for (var participant in _remoteParticipants.toList()) { + for (var pub in participant.trackPublications.values.toList()) { if (pub.subscribed) { pub.sendUpdateTrackSettings(); } @@ -936,8 +936,8 @@ class Room extends DisposableChangeNotifier with EventsEmittable { final trackSids = []; final trackSidsDisabled = []; - for (var participant in _remoteParticipants) { - for (var track in participant.trackPublications.values) { + for (var participant in _remoteParticipants.toList()) { + for (var track in participant.trackPublications.values.toList()) { if (track.subscribed != autoSubscribe) { trackSids.add(track.sid); } @@ -1084,7 +1084,7 @@ extension RoomHardwareManagementMethods on Room { /// Set audio output device. Future setAudioOutputDevice(MediaDevice device) async { if (lkPlatformIs(PlatformType.web)) { - for (final participant in _remoteParticipants) { + for (final participant in _remoteParticipants.toList()) { for (final audioTrack in participant.audioTrackPublications) { audioTrack.track?.setSinkId(device.deviceId); } diff --git a/lib/src/core/signal_client.dart b/lib/src/core/signal_client.dart index 47ce0883..93f0f51d 100644 --- a/lib/src/core/signal_client.dart +++ b/lib/src/core/signal_client.dart @@ -517,10 +517,11 @@ extension SignalClientInternalMethods on SignalClient { // queue is empty if (_queue.isEmpty) return; // send requests - for (final request in _queue) { + final queueCopy = List.of(_queue); + _queue.clear(); + for (final request in queueCopy) { _sendRequest(request, enqueueIfReconnecting: false); } - _queue.clear(); } @internal diff --git a/lib/src/e2ee/e2ee_manager.dart b/lib/src/e2ee/e2ee_manager.dart index 3241a4e8..f8d21de9 100644 --- a/lib/src/e2ee/e2ee_manager.dart +++ b/lib/src/e2ee/e2ee_manager.dart @@ -141,7 +141,7 @@ class E2EEManager { await _listener?.cancelAll(); await _listener?.dispose(); _listener = null; - for (var frameCryptor in _frameCryptors.values) { + for (var frameCryptor in _frameCryptors.values.toList()) { await frameCryptor.dispose(); } _frameCryptors.clear(); @@ -178,7 +178,7 @@ class E2EEManager { /// without encryption/decryption Future setEnabled(bool enabled) async { _enabled = enabled; - for (var frameCryptor in _frameCryptors.entries) { + for (var frameCryptor in _frameCryptors.entries.toList()) { await frameCryptor.value.setEnabled(enabled); } } @@ -189,7 +189,7 @@ class E2EEManager { /// if null, use local participant. Future setKeyIndex(int keyIndex, {String? participantIdentity}) async { participantIdentity ??= _room?.localParticipant?.identity; - for (var item in _frameCryptors.entries) { + for (var item in _frameCryptors.entries.toList()) { if (item.key.keys.first == participantIdentity) { await item.value.setKeyIndex(keyIndex); } @@ -202,7 +202,7 @@ class E2EEManager { /// @return the key index and -1 if not found Future getKeyIndex(String? participantIdentity) async { participantIdentity ??= _room?.localParticipant?.identity; - for (var item in _frameCryptors.entries) { + for (var item in _frameCryptors.entries.toList()) { if (item.key.keys.first == participantIdentity) { return await item.value.keyIndex; } diff --git a/lib/src/managers/event.dart b/lib/src/managers/event.dart index 2e6cc120..7e915865 100644 --- a/lib/src/managers/event.dart +++ b/lib/src/managers/event.dart @@ -133,7 +133,9 @@ abstract class EventsListenable extends Disposable { if (_listeners.isNotEmpty) { // Stop listening to all events logger.finer('${objectId} cancelling ${_listeners.length} listeners(s)'); - for (final listener in _listeners) { + final listenersCopy = List.of(_listeners); + _listeners.clear(); + for (final listener in listenersCopy) { await listener.cancel(); } } diff --git a/lib/src/track/web/_audio_html.dart b/lib/src/track/web/_audio_html.dart index f69f51ca..e186c044 100644 --- a/lib/src/track/web/_audio_html.dart +++ b/lib/src/track/web/_audio_html.dart @@ -52,7 +52,7 @@ Future startAudio(String id, rtc.MediaStreamTrack track) async { } Future startAllAudioElement() async { - for (final el in _audioElements.values) { + for (final el in _audioElements.values.toList()) { if (el.instanceOfString('HTMLAudioElement')) { final audio = el as web.HTMLAudioElement; await audio.play().toDart;