Repository navigation
Conversation
draw() snapshots multiPreviewSurfaceManagers with values.toList(). For a single-element collection Kotlin's toList() reads size and then calls iterator().next() as a separate step. If that surface is removed from another thread in between (removeMultiPreviewSurface() on the main thread while draw() runs on the GL thread), next() throws NoSuchElementException on the GL thread, and without a render error callback the app crashes. ArrayList(values) copies through ConcurrentHashMap's toArray(), which tolerates concurrent changes, so a surface removed meanwhile is simply in or out of this frame's snapshot, as it already is with two or more. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
GlStreamInterface.draw()takes a snapshot of the extra preview surfaces withmultiPreviewSurfaceManagers.values.toList(). For a collection of exactly one element, Kotlin'stoList()readssizeand then callsiterator().next()as a separate step. If that surface is removed from another thread in between (in our appremoveMultiPreviewSurface()is called from the main thread, whiledraw()runs on the GL thread), the iterator is already empty andnext()throwsNoSuchElementException. Without aRenderErrorCallbackthe exception is rethrown on the GL thread and the app crashes.With two or more surfaces
toList()already copies through anArrayList, so only the common case of one extra preview is affected.We hit this in production with 2.8.0 (the same line, 346 there): first as a crash on a Samsung Galaxy S24 Ultra (Android 16), and, after we set a
RenderErrorCallbackthat skips the frame, again on a Vivo phone (Android 16). In both cases the exception came from theConcurrentHashMapiterator insidetoList()indraw().Change
Take the snapshot with
ArrayList(multiPreviewSurfaceManagers.values).ArrayListcopies throughConcurrentHashMap'stoArray(), which tolerates concurrent changes, so a surface removed meanwhile is simply in or out of this frame's snapshot, as already happens with two or more surfaces. One line, plus a comment so it isn't changed back totoList().Testing
./gradlew :library:compileReleaseKotlinand./gradlew :library:testDebugUnitTest(22 tests) pass locally.ConcurrentHashMap, another snapshotsvaluesthe waydraw()does. This is an artificial worst case, only to show the mechanism:toList()threwNoSuchElementException1,839,677 times in 6,835,364 snapshots in 5 s;ArrayList(values)threw 0 times in 29,150,687.🤖 Generated with Claude Code