Skip to content

ConcurrentModificationException in MediaLibrarySessionImpl.getSubscribedControllers() #3056

Closed

Description

@jjohnstz

Version

Media3 1.8.0

More version details

No response

Devices that reproduce the issue

Pixel 10 Pro Fold, Pixel 9 Pro XL, Pixel 8, Pixel 6, Galaxy S20, Galaxy S23 Ultra

Devices that do not reproduce the issue

No response

Reproducible in the demo app?

Not tested

Reproduction steps

Not able to reproduce, crash information is from Crashlytics

Expected result

No exception thrown

Actual result

Fatal Exception: java.util.ConcurrentModificationException:
       at com.google.common.collect.CompactHashSet$1.checkForConcurrentModification(CompactHashSet.java:582)
       at com.google.common.collect.CompactHashSet$1.next(CompactHashSet.java:556)
       at com.google.common.collect.AbstractMapBasedMultimap$WrappedCollection$WrappedIterator.next(AbstractMapBasedMultimap.java:465)
       at java.util.AbstractCollection.toArray(AbstractCollection.java:146)
       at com.google.common.collect.ImmutableList.copyOf(ImmutableList.java:264)
       at androidx.media3.session.MediaLibrarySessionImpl.getSubscribedControllers(MediaLibrarySessionImpl.java:246)
       at androidx.media3.session.MediaLibraryService$MediaLibrarySession.getSubscribedControllers(MediaLibraryService.java:798)
       at fm.libro.service.session.LibroMediaSession$subscribeToMediaItemsChanges$1$1.invokeSuspend(LibroMediaSession.kt:24)
       at fm.libro.service.session.LibroMediaSession$subscribeToMediaItemsChanges$1$1.invoke(:12)
       at fm.libro.service.session.LibroMediaSession$subscribeToMediaItemsChanges$1$1.invoke(:8)
       at kotlinx.coroutines.flow.FlowKt__TransformKt$onEach$$inlined$unsafeTransform$1$2.emit(Emitters.kt:50)
       at kotlinx.coroutines.flow.FlowKt__ChannelsKt.emitAllImpl$FlowKt__ChannelsKt(Channels.kt:33)
       at kotlinx.coroutines.flow.FlowKt__ChannelsKt.access$emitAllImpl$FlowKt__ChannelsKt(Channels.kt:1)
       at kotlinx.coroutines.flow.FlowKt__ChannelsKt$emitAllImpl$1.invokeSuspend(:14)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
       at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1154)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:652)
       at java.lang.Thread.run(Thread.java:1563)

Media

NA

Bug Report

Activity

  1. marcbaechinger commented on Feb 5, 2026

    @marcbaechinger
    Contributor

    Thanks for reporting!

    If you find some time to tell us a bit about the threading model of your app in which that call is executed and producing the exception that would be great. I have some assumptions, but I think it's more efficient to get some information from the pros. It would allow me to try to reproduce exact your case and make a unit test to test the regression.

    I'm aware you can't reproduce yourself. Let us know about the app architecture specifically the threading. This may help us to a) give you some ideas how you could fix that on your end without needing to wait for a fix from our end, and b) to fix this on our end to either make it thread safe or add some checks from what threads a given method is allowed to be accessed.

  2. jjohnstz commented on Feb 10, 2026

    @jjohnstz
    Author

    getSubscribedControllers() is being called from a Kotlin coroutine running on a custom IO thread pool (unbounded Executors.newCachedThreadPool()), not from the main thread.

    We have a coroutine flow that monitors database changes and notifies subscribed controllers:

    mediaRepo.getFolderUpdateFlow(folder)
        .onEach { count ->
            val controllers = mediaLibrarySession?.getSubscribedControllers(mediaId) // ← CRASH
            controllers?.forEach { controller ->
                mediaLibrarySession?.notifyChildrenChanged(controller, mediaId, count, null)
            }
        }
        .launchIn(ioScope) // Custom IO thread pool
    

    The issue

    Thread A (our IO pool thread): Calls getSubscribedControllers() → iterates collection
    Thread B (Media3's thread): Controller subscribes/unsubscribes → modifies collection
    Result: ConcurrentModificationException in ImmutableList.copyOf()

    Question

    Should getSubscribedControllers() only be called from the main thread? If yes, we can wrap it with:

    withContext(Dispatchers.Main) {
        mediaLibrarySession?.getSubscribedControllers(mediaId)
    }
    

    Or should the method be made thread-safe for calls from any thread?

  3. marcbaechinger commented on Feb 10, 2026

    @marcbaechinger
    Contributor

    Thanks for confirming!

    Or should the method be made thread-safe for calls from any thread?

    All methods on 'MediaSession' must be thread safe as per documentation. We have a fix for this and will update this issue when it landed in main.

    Thank you for reporting and adding details, very much appreciated!

  4. marcbaechinger commented on Feb 11, 2026

    @marcbaechinger
    Contributor

    The commit above should fix this.

  5. locked and limited conversation to collaborators on Apr 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions