-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Describe the bug
MultiLayerComposeScene and SingleLayerComposeScene provide the option to set a coroutineContext parameter.
FrameDispatcher also provides the option to pass a CoroutineScope or CoroutineContext.
However, using a scene with any other dispatcher other than MainUIDispatcher (provided by Skiko) will result in deadlock at some point when using the application.
Since the render call on a scene must also be called from the same thread, this also means that the FrameDispatcher must use MainUIDispatcher as well.
This causes several issues. For example, GLFW requires the use of a specific thread to be able to draw to a Window, which clashes with the requirement for MainUIDispatcher which is an AWT event queue.
The following seems to be unsupported for now: I consider this a major bug, since this makes it impossible to render multiple scenes at once using different threads (as MainUIDispatcher is always the same). Multiple scenes are needed if you wish to render multiple completely separate scenes in one Kotlin application. The coroutineContext paramters are also completly obsolete this way.
Please note: In past Compose versions (for example 1.2.0) ComposeScene worked fine with other dispatchers, so this is a new bug which has been introduced with recent updates.
To Reproduce
- create a custom
MultiLayerComposeScene - pass a
Dispatchers.Default.limitedParallelism(1)to it - use the same limited dispatcher for the
FrameDispatcher - render the scene continuously using the
FrameDispatcher - do something, e.g. scroll a lot etc
- observe deadlock in IntelliJ ThreadDump using debugger
Deadlock:
Detailed Thread Dump Stack Traces
"AWT-EventQueue-0 @coroutine#8@24000" tid=0x9a nid=NA waiting for monitor entry
java.lang.Thread.State: BLOCKED
blocks DefaultDispatcher-worker-5 @coroutine#5@23693
waiting for DefaultDispatcher-worker-5 @coroutine#5@23693 to release lock on <0x5e17> (a androidx.compose.runtime.SynchronizedObject)
at androidx.compose.runtime.BroadcastFrameClock.getHasAwaiters(Synchronization.kt:33)
at androidx.compose.runtime.Recomposer.getHasBroadcastFrameClockAwaitersLocked(Recomposer.kt:289)
at androidx.compose.runtime.Recomposer.deriveStateLocked(Recomposer.kt:326)
at androidx.compose.runtime.Recomposer.access$deriveStateLocked(Recomposer.kt:127)
at androidx.compose.runtime.Recomposer$recompositionRunner$2$unregisterApplyObserver$1.invoke(Recomposer.kt:989)
- locked <0x5e19> (a androidx.compose.runtime.SynchronizedObject)
at androidx.compose.runtime.Recomposer$recompositionRunner$2$unregisterApplyObserver$1.invoke(Recomposer.kt:976)
at androidx.compose.runtime.snapshots.SnapshotKt.advanceGlobalSnapshot(Snapshot.kt:1816)
at androidx.compose.runtime.snapshots.SnapshotKt.advanceGlobalSnapshot(Snapshot.kt:1831)
at androidx.compose.runtime.snapshots.SnapshotKt.access$advanceGlobalSnapshot(Snapshot.kt:1)
at androidx.compose.runtime.snapshots.Snapshot$Companion.sendApplyNotifications(Snapshot.kt:584)
at androidx.compose.ui.platform.GlobalSnapshotManager$ensureStarted$1.invokeSuspend(GlobalSnapshotManager.skiko.kt:46)
"DefaultDispatcher-worker-5 @coroutine#5@23693" tid=0x73 nid=NA waiting for monitor entry
java.lang.Thread.State: BLOCKED
blocks AWT-EventQueue-0 @coroutine#8@24000
waiting for AWT-EventQueue-0 @coroutine#8@24000 to release lock on <0x5e19> (a androidx.compose.runtime.SynchronizedObject)
at androidx.compose.runtime.Recomposer.composeInitial$runtime(Synchronization.kt:33)
at androidx.compose.runtime.ComposerImpl$CompositionContextImpl.composeInitial$runtime(Composer.kt:3600)
at androidx.compose.runtime.CompositionImpl.composeInitial(Composition.kt:633)
at androidx.compose.runtime.CompositionImpl.setContentWithReuse(Composition.kt:625)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcomposeInto(SubcomposeLayout.kt:502)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:472)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:463)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:447)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$Scope.subcompose(SubcomposeLayout.kt:872)
at androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScopeImpl.measure-0kLqBqw(LazyLayoutMeasureScope.kt:125)
at androidx.compose.foundation.lazy.LazyListMeasuredItemProvider.getAndMeasure(LazyListMeasuredItemProvider.kt:48)
at androidx.compose.foundation.lazy.LazyListMeasureKt.measureLazyList-5IMabDg(LazyListMeasure.kt:195)
at androidx.compose.foundation.lazy.LazyListKt$rememberLazyListMeasurePolicy$1$1.invoke-0kLqBqw(LazyList.kt:313)
at androidx.compose.foundation.lazy.LazyListKt$rememberLazyListMeasurePolicy$1$1.invoke(LazyList.kt:178)
at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$3$2$1.invoke-0kLqBqw(LazyLayout.kt:107)
at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$3$2$1.invoke(LazyLayout.kt:100)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$createMeasurePolicy$1.measure-3p2s80s(SubcomposeLayout.kt:709)
at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:126)
at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s(GraphicsLayerModifier.kt:646)
at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:252)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:251)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:132)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:504)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:260)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui(OwnerSnapshotObserver.kt:133)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui(OwnerSnapshotObserver.kt:113)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1617)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:36)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:620)
at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui(LayoutNode.kt:1145)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure-sdFAvZA(MeasureAndLayoutDelegate.kt:354)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.measureAndLayout-0kLqBqw(MeasureAndLayoutDelegate.kt:439)
at androidx.compose.ui.node.RootNodeOwner$OwnerImpl.measureAndLayout-0kLqBqw(RootNodeOwner.skiko.kt:322)
at androidx.compose.ui.node.LayoutNode.forceRemeasure(LayoutNode.kt:1219)
at androidx.compose.foundation.lazy.LazyListState.onScroll$foundation(LazyListState.kt:352)
at androidx.compose.foundation.lazy.LazyListState$scrollableState$1.invoke(LazyListState.kt:187)
at androidx.compose.foundation.lazy.LazyListState$scrollableState$1.invoke(LazyListState.kt:187)
at androidx.compose.foundation.gestures.DefaultScrollableState$scrollScope$1.scrollBy(ScrollableState.kt:166)
at androidx.compose.foundation.gestures.ScrollingLogic$dispatchScroll$performScroll$1.invoke-MK-Hz9U(Scrollable.kt:693)
at androidx.compose.foundation.gestures.ScrollingLogic$dispatchScroll$performScroll$1.invoke(Scrollable.kt:683)
at androidx.compose.foundation.gestures.ScrollingLogic.dispatchScroll-3eAAhYA(Scrollable.kt:707)
at androidx.compose.foundation.gestures.MouseWheelScrollNode.dispatchMouseWheelScroll(MouseWheelScrollable.kt:312)
at androidx.compose.foundation.gestures.MouseWheelScrollNode.access$dispatchMouseWheelScroll(MouseWheelScrollable.kt:54)
at androidx.compose.foundation.gestures.MouseWheelScrollNode$animateMouseWheelScroll$2.invoke(MouseWheelScrollable.kt:297)
at androidx.compose.foundation.gestures.MouseWheelScrollNode$animateMouseWheelScroll$2.invoke(MouseWheelScrollable.kt:287)
at androidx.compose.animation.core.SuspendAnimationKt.doAnimationFrame(SuspendAnimation.kt:361)
at androidx.compose.animation.core.SuspendAnimationKt.doAnimationFrameWithScale(SuspendAnimation.kt:339)
at androidx.compose.animation.core.SuspendAnimationKt.access$doAnimationFrameWithScale(SuspendAnimation.kt:1)
at androidx.compose.animation.core.SuspendAnimationKt$animate$9.invoke(SuspendAnimation.kt:279)
at androidx.compose.animation.core.SuspendAnimationKt$animate$9.invoke(SuspendAnimation.kt:278)
at androidx.compose.animation.core.SuspendAnimationKt$callWithFrameNanos$2.invoke(SuspendAnimation.kt:304)
at androidx.compose.animation.core.SuspendAnimationKt$callWithFrameNanos$2.invoke(SuspendAnimation.kt:303)
at androidx.compose.runtime.BroadcastFrameClock$FrameAwaiter.resume(BroadcastFrameClock.kt:42)
at androidx.compose.runtime.BroadcastFrameClock.sendFrame(BroadcastFrameClock.kt:71)
- locked <0x5e17> (a androidx.compose.runtime.SynchronizedObject)
at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$1.invoke(Recomposer.kt:558)
at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$1.invoke(Recomposer.kt:551)
at androidx.compose.runtime.BroadcastFrameClock$FrameAwaiter.resume(BroadcastFrameClock.kt:42)
at androidx.compose.runtime.BroadcastFrameClock.sendFrame(BroadcastFrameClock.kt:71)
- locked <0x5e18> (a androidx.compose.runtime.SynchronizedObject)
at androidx.compose.ui.scene.BaseComposeScene.render(BaseComposeScene.skiko.kt:160)
Expected behavior
It should be possible to pass a custom dispatcher to the scene APIs. E.g. your own Dispatchers.Default.limitedParallelism(1). Alternatively, it could be possible to configure what MainUIDispatcher actually is under the hood.
This is needed for having multiple and completely separate scenes at once in one Kotlin application. (not supported, but it is still needed for reasons explained above)
Custom scenes should not depend on the AWT event queue, since they won't be rendered to an AWT or Swing Window anyways.
Affected platforms
- Desktop (Windows, Linux, macOS)
Versions
- Libraries:
- Compose Multiplatform version:
1.6.2and1.6.10-rc01
- Compose Multiplatform version:
- Kotlin version:
1.9.23 - OS version(s) (required for Desktop and iOS issues): Windows 11 and Linux
- OS architecture (x86 or arm64):
x86 - JDK (for desktop issues):
21
Additional context
- with previous versions (when
ComposeScenewas still completely provided by Skiko) the API worked perfectly fine with any dispatcher - Compose Multiplatform with its own use of FrameDispatcher and ComposeScenes on the desktop seems to always use the
MainUIDispatcher, which would explain why this issue has not occurred yet.
