JetBrains / compose-multiplatform

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
https://jetbrains.com/lp/compose-multiplatform
Apache License 2.0
16.15k stars 1.17k forks source link

[Linux Crash] IllegalArgumentException: LayoutNode@4b74ca9d children: 1 measurePolicy ... is not ready. layoutState is NeedsRelayout #481

Closed jimgoog closed 3 years ago

jimgoog commented 3 years ago

The following code throws an exception on Linux when the user clicks the button:

var b by mutableStateOf(false)
fun main() = Window {
    if(b) {
        Text("Clickable", Modifier.clickable(onClick={}))
        TextField("text", {})
    }
    Button(onClick={b=true}) {
        Text("Click me to trigger crash")
    }
}

Interestingly, the code does not crash on Mac.

The exception thrown on Linux is:

Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: LayoutNode@4b74ca9d children: 1 measurePolicy: androidx.compose.foundation.layout.BoxKt$boxMeasurePolicy$1@14d2bb7c is not ready. layoutState is NeedsRelayout
        at androidx.compose.ui.node.InnerPlaceable.performDraw(InnerPlaceable.kt:119)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.LayoutNode.draw$ui(LayoutNode.kt:790)
        at androidx.compose.ui.node.InnerPlaceable.performDraw(InnerPlaceable.kt:122)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.performDraw(DelegatingLayoutNodeWrapper.kt:70)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.performDraw(DelegatingLayoutNodeWrapper.kt:70)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.performDraw(DelegatingLayoutNodeWrapper.kt:70)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.performDraw(DelegatingLayoutNodeWrapper.kt:70)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.performDraw(DelegatingLayoutNodeWrapper.kt:70)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.performDraw(DelegatingLayoutNodeWrapper.kt:70)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.performDraw(DelegatingLayoutNodeWrapper.kt:70)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.performDraw(DelegatingLayoutNodeWrapper.kt:70)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.performDraw(DelegatingLayoutNodeWrapper.kt:70)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.performDraw(DelegatingLayoutNodeWrapper.kt:70)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.performDraw(DelegatingLayoutNodeWrapper.kt:70)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.LayoutNodeDrawScope.drawContent(LayoutNodeDrawScope.kt:42)
        at androidx.compose.ui.draw.DrawBackgroundModifier.draw(DrawModifier.kt:101)
        at androidx.compose.ui.node.ModifiedDrawNode.performDraw(ModifiedDrawNode.kt:109)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.LayoutNodeDrawScope.drawContent(LayoutNodeDrawScope.kt:42)
        at androidx.compose.foundation.Background.draw(Background.kt:107)
        at androidx.compose.ui.node.ModifiedDrawNode.performDraw(ModifiedDrawNode.kt:109)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.ModifiedLayoutNode.performDraw(ModifiedLayoutNode.kt:80)
        at androidx.compose.ui.node.LayoutNodeWrapper$invoke$2.invoke(LayoutNodeWrapper.kt:209)
        at androidx.compose.ui.node.LayoutNodeWrapper$invoke$2.invoke(LayoutNodeWrapper.kt:208)
        at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:1714)
        at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:116)
        at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui(OwnerSnapshotObserver.kt:75)
        at androidx.compose.ui.node.LayoutNodeWrapper.invoke(LayoutNodeWrapper.kt:208)
        at androidx.compose.ui.node.LayoutNodeWrapper.invoke(LayoutNodeWrapper.kt:50)
        at androidx.compose.ui.platform.SkijaLayer.performDrawLayer(SkijaLayer.desktop.kt:205)
        at androidx.compose.ui.platform.SkijaLayer.drawLayer(SkijaLayer.desktop.kt:171)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:190)
        at androidx.compose.ui.node.LayoutNode.draw$ui(LayoutNode.kt:790)
        at androidx.compose.ui.node.InnerPlaceable.performDraw(InnerPlaceable.kt:122)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.performDraw(DelegatingLayoutNodeWrapper.kt:70)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.performDraw(DelegatingLayoutNodeWrapper.kt:70)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.performDraw(DelegatingLayoutNodeWrapper.kt:70)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.DelegatingLayoutNodeWrapper.performDraw(DelegatingLayoutNodeWrapper.kt:70)
        at androidx.compose.ui.node.LayoutNodeWrapper.draw(LayoutNodeWrapper.kt:195)
        at androidx.compose.ui.node.LayoutNode.draw$ui(LayoutNode.kt:790)
        at androidx.compose.ui.platform.DesktopOwner.draw(DesktopOwner.desktop.kt:282)
        at androidx.compose.ui.platform.DesktopOwner.render(DesktopOwner.desktop.kt:209)
        at androidx.compose.ui.platform.DesktopOwners.onFrame(DesktopOwners.desktop.kt:121)
        at androidx.compose.desktop.ComposeLayer$1.onRender(ComposeLayer.desktop.kt:123)
        at org.jetbrains.skiko.SkiaLayer.update$skiko(SkiaLayer.kt:117)
        at org.jetbrains.skiko.redrawer.LinuxOpenGLRedrawer.update(LinuxOpenGLRedrawer.kt:47)
        at org.jetbrains.skiko.redrawer.LinuxOpenGLRedrawer.access$update(LinuxOpenGLRedrawer.kt:13)
        at org.jetbrains.skiko.redrawer.LinuxOpenGLRedrawer$Companion$frameDispatcher$1.invokeSuspend(LinuxOpenGLRedrawer.kt:68)
        at org.jetbrains.skiko.redrawer.LinuxOpenGLRedrawer$Companion$frameDispatcher$1.invoke(LinuxOpenGLRedrawer.kt)
        at org.jetbrains.skiko.FrameDispatcher$job$1.invokeSuspend(FrameDispatcher.kt:32)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:316)
        at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
        at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
        at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
        at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
        at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
        at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
        at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
        at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
        at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
        at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
        at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

cc @igordmn

igordmn commented 3 years ago

Thanks!

It crashes on Windows too

igordmn commented 3 years ago

the code does not crash on Mac.

It is because of the different Font metrics.

This code crashes on Android too:

val state = remember { derivedStateOf { 1 } } // derivedStateOf is used in Modifier.indication

Canvas(Modifier.size(20.dp)) {
    // reading derivedStateOf implicitly forces Snapshot.sendApplyNotifications(),
    // and so requestRelayout
    state.value
}

BasicTextField(
    TextFieldValue("text"),
    {},
    textStyle = TextStyle(fontSize = 100.sp, textIndent = TextIndent(firstLine = 10.sp)),
    modifier = Modifier.requiredSize(20.dp)
)

Inside BasicTextField we change mutableStateOf during layout stage, by this we schedule applying state snapshot. During the draw stage we force applying snapshot, and so mark all changed nodes as NeedsRelayout.

More simple reproducer:

var b by mutableStateOf(0)

fun main() = Window {
    val state = derivedStateOf { 1 }
    Canvas(Modifier.size(20.dp)) {
        println("DRAW")
        state.value
    }

    Box {
        Box {
            b
        }

        Layout({}, measurePolicy = { _, _ ->
            layout(10, 10) {
                println("LAYOUT")
                b++
            }
        })
    }
}

What we can do:

  1. don't change mutableStateOf on layout stage (inside Modifier.textFieldScroll)
  2. don't apply global snapshot when we read derivedStateOf (don't know how we can achieve this, we need to investigate how we can keep derivedStateOf fresh without applying snapshot)

P.S. BTW, maybe in the future we should prohibit requestRemeasure/requestRelayout/changing mutableStateOf during recomposition/layout/draw? There are a lot of bugs regarding them.

olonho commented 3 years ago

@jimgoog looks like pretty generic issue to be considered on all Compose platforms.

andkulikov commented 3 years ago

Filled https://issuetracker.google.com/issues/182821580. Igor, thanks for the investigation

andkulikov commented 3 years ago

FYI this assertion was removed https://android-review.googlesource.com/c/platform/frameworks/support/+/1640305/

okushnikov commented 2 months ago

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.