Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Codeviewer. Crash on Ubuntu #991

Closed
igordmn opened this issue Aug 3, 2021 · 12 comments
Closed

Codeviewer. Crash on Ubuntu #991

igordmn opened this issue Aug 3, 2021 · 12 comments
Assignees
Milestone

Comments

@igordmn
Copy link
Collaborator

igordmn commented Aug 3, 2021

Ubuntu 20.04.2 LTS, Gnome 3.36.8, X11

  1. Run codeviewer
  2. Click on some tree elements until the app crashes:
Exception in thread "main" java.lang.IllegalStateException: pending composition has not been applied
        at androidx.compose.runtime.CompositionImpl.drainPendingModificationsForCompositionLocked(Composition.kt:443)
        at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:611)
        at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:764)
        at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:103)
        at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:447)
        at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:416)
        at androidx.compose.ui.window.YieldFrameClock.withFrameNanos(Application.desktop.kt:210)
        at androidx.compose.ui.window.YieldFrameClock$withFrameNanos$1.invokeSuspend(Application.desktop.kt)
        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:708)
        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)

(stacktrace in 1.0.0-alpha1-rc5)

Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: pending composition has not been applied
        at androidx.compose.runtime.CompositionImpl.drainPendingModificationsForCompositionLocked(Composition.kt:435)
        at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:578)
        at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:763)
        at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:102)
        at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:446)
        at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:415)
        at androidx.compose.runtime.BroadcastFrameClock$FrameAwaiter.resume(BroadcastFrameClock.kt:42)
        at androidx.compose.runtime.BroadcastFrameClock.sendFrame(BroadcastFrameClock.kt:63)
        at androidx.compose.ui.platform.DesktopOwners.onFrame(DesktopOwners.desktop.kt:120)
        at androidx.compose.desktop.ComposeLayer$1.onRender(ComposeLayer.desktop.kt:122)
        at org.jetbrains.skiko.SkiaLayer.update$skiko(SkiaLayer.kt:292)
        at org.jetbrains.skiko.redrawer.LinuxOpenGLRedrawer.update(LinuxOpenGLRedrawer.kt:52)
        at org.jetbrains.skiko.redrawer.LinuxOpenGLRedrawer.access$update(LinuxOpenGLRedrawer.kt:14)
        at org.jetbrains.skiko.redrawer.LinuxOpenGLRedrawer$Companion$frameDispatcher$1.invokeSuspend(LinuxOpenGLRedrawer.kt:75)
        at org.jetbrains.skiko.redrawer.LinuxOpenGLRedrawer$Companion$frameDispatcher$1.invoke(LinuxOpenGLRedrawer.kt)
        at org.jetbrains.skiko.FrameDispatcher$job$1.invokeSuspend(FrameDispatcher.kt:36)
        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:708)
        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)

(stacktrace in 0.4.0-build208)

0.4.0-build208 + Kotlin 1.5.0 - crash happens
0.4.0-build190 + Kotlin 1.4.32 - crash doesn't happen

@igordmn igordmn self-assigned this Aug 3, 2021
@igordmn
Copy link
Collaborator Author

igordmn commented Aug 3, 2021

Similar issue in Google tracker:

@yaroslavkulinich
Copy link

The same on Windows. No logical steps to reproduce constantly, it just crashes from time to time.
I use 1.0.0-alpha3.
Would be great to know how to avoid this (even some workaround).

@yaroslavkulinich
Copy link

@igordmn Any news with this kind of trouble? Maybe there are some suggestions on when to expect and how to avoid it?
Because one of my components crashes in different cases. Some repeatable cases (from time to time) are: navigating back to the previous screen (pop) and using the popup menu. But I still can't understand what part of code in that component leads to the problem.
Thank you.

@igordmn
Copy link
Collaborator Author

igordmn commented Sep 15, 2021

Any news with this kind of trouble

Unfortunately, no. To fix this issue, we need to make a minimal reliable reproducer, using only compose.runtime (it seems the bug is inside it).

@yaroslavkulinich
Copy link

@igordmn
After hours of monkey-testing, I discovered that the "pending composition has not been applied" exception, in my case, was related to LazyColumn. After replacing LazyColumn with Column, everything is fine.
I tested directly in my app with complex UI hierarchies, so, unfortunately, can't provide a reproducer. Maybe when will have more time, I will simplify the app code to minimal needed elements and make a reproducer.

One more thing. The exception happens on Windows in my case. On Mac everything is fine.

@igordmn igordmn added the p:high High priority label Nov 2, 2021
@igordmn igordmn added this to the 1.0 milestone Nov 2, 2021
igordmn added a commit to JetBrains/compose-multiplatform-core that referenced this issue Nov 16, 2021
Fixes JetBrains/compose-multiplatform#991

Eclipse OpenJ9 can return negative hashcodes (at least 15.0.2):
-1366493741
192407236

when we compare them, we can catch an overflow:
`-1366493741 - 1924072363` will be more than 0

see eclipse-openj9/openj9#3062

That leads to wrong behaviour of IdentityArraySet, and so of Recomposer:
```
alreadyComposed.add(composition)
println(alreadyComposed.contains(composition)) // will print `false`
```

The crash:
```
Caused by: java.lang.IllegalStateException: pending composition has not been applied
	at androidx.compose.runtime.CompositionImpl.drainPendingModificationsForCompositionLocked(Composition.kt:448)
	at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:635)
	at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:793)
	at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:106)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:460)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:426)
	at androidx.compose.desktop.examples.example1.TestClock.withFrameNanos(Main.jvm.kt:85)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2.invokeSuspend(Recomposer.kt:426)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
	at java.base/java.lang.Thread.run(Thread.java:853)
```

The reproducer:
```
import androidx.compose.runtime.AbstractApplier
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MonotonicFrameClock
import androidx.compose.runtime.currentRecomposeScope
import androidx.compose.runtime.rememberCompositionContext
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.withRunningRecomposer
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield
import java.util.concurrent.Executors.newSingleThreadExecutor
import kotlin.random.Random

fun main() {
    runBlocking(newSingleThreadExecutor().asCoroutineDispatcher() + TestClock) {
        repeat(300) { num ->
            println("test $num")

            withRunningRecomposer {
                val composition = Composition(TestApplier, it)
                composition.setContent {
                    val scope = currentRecomposeScope
                    Test(Random.nextInt())
                    Test(Random.nextInt())
                    LaunchedEffect(Unit) {
                        scope.invalidate()
                    }
                }
                yield()
                yield()
            }
        }
    }
}

@composable
fun Test(test: Int) {
    val latestContent = rememberUpdatedState(test)
    latestContent.value

    val parent = rememberCompositionContext()
    LaunchedEffect(Unit) {
        val composition1 = Composition(TestApplier, parent)
        composition1.setContent {
            latestContent.value
        }
        val composition2 = Composition(TestApplier, parent)
        composition2.setContent {
            latestContent.value
        }
    }
}

internal object TestApplier: AbstractApplier<Unit>(Unit) {
    override fun insertTopDown(index: Int, instance: Unit) = Unit
    override fun insertBottomUp(index: Int, instance: Unit) = Unit
    override fun remove(index: Int, count: Int) = Unit
    override fun move(from: Int, to: Int, count: Int) = Unit
    override fun onClear() = Unit
}

object TestClock : MonotonicFrameClock {
    override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R {
        return onFrame(System.nanoTime())
    }
}
```

java --version:
```
openjdk 15.0.2 2021-01-19
OpenJDK Runtime Environment AdoptOpenJDK (build 15.0.2+7)
Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.24.0, JRE 15 Linux amd64-64-Bit Compressed References 20210121_172 (JIT enabled, AOT enabled)
OpenJ9   - 345e1b09e
OMR      - 741e94ea8
JCL      - 863b523566 based on jdk-15.0.2+7)
```
OS: Ubuntu 20.0.4

Test: manual (see the reproducer)
Test: ./gradlew :compose:runtime:runtime:test (without the fix we have failing Identity*Test on this JDK)
@igordmn
Copy link
Collaborator Author

igordmn commented Nov 19, 2021

Should be fixed in 1.0.0-beta6-dev474

@igordmn igordmn closed this as completed Nov 19, 2021
igordmn added a commit to JetBrains/compose-multiplatform-core that referenced this issue Jan 14, 2022
Fixes JetBrains/compose-multiplatform#991

Eclipse OpenJ9 can return negative hashcodes (at least 15.0.2):
-1366493741
192407236

when we compare them, we can catch an overflow:
`-1366493741 - 1924072363` will be more than 0

see eclipse-openj9/openj9#3062

That leads to wrong behaviour of IdentityArraySet, and so of Recomposer:
```
alreadyComposed.add(composition)
println(alreadyComposed.contains(composition)) // will print `false`
```

The crash:
```
Caused by: java.lang.IllegalStateException: pending composition has not been applied
	at androidx.compose.runtime.CompositionImpl.drainPendingModificationsForCompositionLocked(Composition.kt:448)
	at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:635)
	at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:793)
	at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:106)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:460)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:426)
	at androidx.compose.desktop.examples.example1.TestClock.withFrameNanos(Main.jvm.kt:85)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2.invokeSuspend(Recomposer.kt:426)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
	at java.base/java.lang.Thread.run(Thread.java:853)
```

The reproducer:
```
import androidx.compose.runtime.AbstractApplier
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MonotonicFrameClock
import androidx.compose.runtime.currentRecomposeScope
import androidx.compose.runtime.rememberCompositionContext
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.withRunningRecomposer
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield
import java.util.concurrent.Executors.newSingleThreadExecutor
import kotlin.random.Random

fun main() {
    runBlocking(newSingleThreadExecutor().asCoroutineDispatcher() + TestClock) {
        repeat(300) { num ->
            println("test $num")

            withRunningRecomposer {
                val composition = Composition(TestApplier, it)
                composition.setContent {
                    val scope = currentRecomposeScope
                    Test(Random.nextInt())
                    Test(Random.nextInt())
                    LaunchedEffect(Unit) {
                        scope.invalidate()
                    }
                }
                yield()
                yield()
            }
        }
    }
}

@composable
fun Test(test: Int) {
    val latestContent = rememberUpdatedState(test)
    latestContent.value

    val parent = rememberCompositionContext()
    LaunchedEffect(Unit) {
        val composition1 = Composition(TestApplier, parent)
        composition1.setContent {
            latestContent.value
        }
        val composition2 = Composition(TestApplier, parent)
        composition2.setContent {
            latestContent.value
        }
    }
}

internal object TestApplier: AbstractApplier<Unit>(Unit) {
    override fun insertTopDown(index: Int, instance: Unit) = Unit
    override fun insertBottomUp(index: Int, instance: Unit) = Unit
    override fun remove(index: Int, count: Int) = Unit
    override fun move(from: Int, to: Int, count: Int) = Unit
    override fun onClear() = Unit
}

object TestClock : MonotonicFrameClock {
    override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R {
        return onFrame(System.nanoTime())
    }
}
```

java --version:
```
openjdk 15.0.2 2021-01-19
OpenJDK Runtime Environment AdoptOpenJDK (build 15.0.2+7)
Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.24.0, JRE 15 Linux amd64-64-Bit Compressed References 20210121_172 (JIT enabled, AOT enabled)
OpenJ9   - 345e1b09e
OMR      - 741e94ea8
JCL      - 863b523566 based on jdk-15.0.2+7)
```
OS: Ubuntu 20.0.4

Test: manual (see the reproducer)
Test: ./gradlew :compose:runtime:runtime:test (without the fix we have failing Identity*Test on this JDK)
igordmn added a commit to JetBrains/compose-multiplatform-core that referenced this issue Jan 18, 2022
Fixes JetBrains/compose-multiplatform#991

Eclipse OpenJ9 can return negative hashcodes (at least 15.0.2):
-1366493741
192407236

when we compare them, we can catch an overflow:
`-1366493741 - 1924072363` will be more than 0

see eclipse-openj9/openj9#3062

That leads to wrong behaviour of IdentityArraySet, and so of Recomposer:
```
alreadyComposed.add(composition)
println(alreadyComposed.contains(composition)) // will print `false`
```

The crash:
```
Caused by: java.lang.IllegalStateException: pending composition has not been applied
	at androidx.compose.runtime.CompositionImpl.drainPendingModificationsForCompositionLocked(Composition.kt:448)
	at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:635)
	at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:793)
	at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:106)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:460)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:426)
	at androidx.compose.desktop.examples.example1.TestClock.withFrameNanos(Main.jvm.kt:85)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2.invokeSuspend(Recomposer.kt:426)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
	at java.base/java.lang.Thread.run(Thread.java:853)
```

The reproducer:
```
import androidx.compose.runtime.AbstractApplier
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MonotonicFrameClock
import androidx.compose.runtime.currentRecomposeScope
import androidx.compose.runtime.rememberCompositionContext
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.withRunningRecomposer
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield
import java.util.concurrent.Executors.newSingleThreadExecutor
import kotlin.random.Random

fun main() {
    runBlocking(newSingleThreadExecutor().asCoroutineDispatcher() + TestClock) {
        repeat(300) { num ->
            println("test $num")

            withRunningRecomposer {
                val composition = Composition(TestApplier, it)
                composition.setContent {
                    val scope = currentRecomposeScope
                    Test(Random.nextInt())
                    Test(Random.nextInt())
                    LaunchedEffect(Unit) {
                        scope.invalidate()
                    }
                }
                yield()
                yield()
            }
        }
    }
}

@composable
fun Test(test: Int) {
    val latestContent = rememberUpdatedState(test)
    latestContent.value

    val parent = rememberCompositionContext()
    LaunchedEffect(Unit) {
        val composition1 = Composition(TestApplier, parent)
        composition1.setContent {
            latestContent.value
        }
        val composition2 = Composition(TestApplier, parent)
        composition2.setContent {
            latestContent.value
        }
    }
}

internal object TestApplier: AbstractApplier<Unit>(Unit) {
    override fun insertTopDown(index: Int, instance: Unit) = Unit
    override fun insertBottomUp(index: Int, instance: Unit) = Unit
    override fun remove(index: Int, count: Int) = Unit
    override fun move(from: Int, to: Int, count: Int) = Unit
    override fun onClear() = Unit
}

object TestClock : MonotonicFrameClock {
    override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R {
        return onFrame(System.nanoTime())
    }
}
```

java --version:
```
openjdk 15.0.2 2021-01-19
OpenJDK Runtime Environment AdoptOpenJDK (build 15.0.2+7)
Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.24.0, JRE 15 Linux amd64-64-Bit Compressed References 20210121_172 (JIT enabled, AOT enabled)
OpenJ9   - 345e1b09e
OMR      - 741e94ea8
JCL      - 863b523566 based on jdk-15.0.2+7)
```
OS: Ubuntu 20.0.4

Test: manual (see the reproducer)
Test: ./gradlew :compose:runtime:runtime:test (without the fix we have failing Identity*Test on this JDK)
igordmn added a commit to JetBrains/compose-multiplatform-core that referenced this issue Jan 20, 2022
Fixes JetBrains/compose-multiplatform#991

Eclipse OpenJ9 can return negative hashcodes (at least 15.0.2):
-1366493741
192407236

when we compare them, we can catch an overflow:
`-1366493741 - 1924072363` will be more than 0

see eclipse-openj9/openj9#3062

That leads to wrong behaviour of IdentityArraySet, and so of Recomposer:
```
alreadyComposed.add(composition)
println(alreadyComposed.contains(composition)) // will print `false`
```

The crash:
```
Caused by: java.lang.IllegalStateException: pending composition has not been applied
	at androidx.compose.runtime.CompositionImpl.drainPendingModificationsForCompositionLocked(Composition.kt:448)
	at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:635)
	at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:793)
	at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:106)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:460)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:426)
	at androidx.compose.desktop.examples.example1.TestClock.withFrameNanos(Main.jvm.kt:85)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2.invokeSuspend(Recomposer.kt:426)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
	at java.base/java.lang.Thread.run(Thread.java:853)
```

The reproducer:
```
import androidx.compose.runtime.AbstractApplier
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MonotonicFrameClock
import androidx.compose.runtime.currentRecomposeScope
import androidx.compose.runtime.rememberCompositionContext
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.withRunningRecomposer
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield
import java.util.concurrent.Executors.newSingleThreadExecutor
import kotlin.random.Random

fun main() {
    runBlocking(newSingleThreadExecutor().asCoroutineDispatcher() + TestClock) {
        repeat(300) { num ->
            println("test $num")

            withRunningRecomposer {
                val composition = Composition(TestApplier, it)
                composition.setContent {
                    val scope = currentRecomposeScope
                    Test(Random.nextInt())
                    Test(Random.nextInt())
                    LaunchedEffect(Unit) {
                        scope.invalidate()
                    }
                }
                yield()
                yield()
            }
        }
    }
}

@composable
fun Test(test: Int) {
    val latestContent = rememberUpdatedState(test)
    latestContent.value

    val parent = rememberCompositionContext()
    LaunchedEffect(Unit) {
        val composition1 = Composition(TestApplier, parent)
        composition1.setContent {
            latestContent.value
        }
        val composition2 = Composition(TestApplier, parent)
        composition2.setContent {
            latestContent.value
        }
    }
}

internal object TestApplier: AbstractApplier<Unit>(Unit) {
    override fun insertTopDown(index: Int, instance: Unit) = Unit
    override fun insertBottomUp(index: Int, instance: Unit) = Unit
    override fun remove(index: Int, count: Int) = Unit
    override fun move(from: Int, to: Int, count: Int) = Unit
    override fun onClear() = Unit
}

object TestClock : MonotonicFrameClock {
    override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R {
        return onFrame(System.nanoTime())
    }
}
```

java --version:
```
openjdk 15.0.2 2021-01-19
OpenJDK Runtime Environment AdoptOpenJDK (build 15.0.2+7)
Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.24.0, JRE 15 Linux amd64-64-Bit Compressed References 20210121_172 (JIT enabled, AOT enabled)
OpenJ9   - 345e1b09e
OMR      - 741e94ea8
JCL      - 863b523566 based on jdk-15.0.2+7)
```
OS: Ubuntu 20.0.4

Test: manual (see the reproducer)
Test: ./gradlew :compose:runtime:runtime:test (without the fix we have failing Identity*Test on this JDK)
igordmn added a commit to JetBrains/compose-multiplatform-core that referenced this issue Jan 20, 2022
Fixes JetBrains/compose-multiplatform#991

Eclipse OpenJ9 can return negative hashcodes (at least 15.0.2):
-1366493741
192407236

when we compare them, we can catch an overflow:
`-1366493741 - 1924072363` will be more than 0

see eclipse-openj9/openj9#3062

That leads to wrong behaviour of IdentityArraySet, and so of Recomposer:
```
alreadyComposed.add(composition)
println(alreadyComposed.contains(composition)) // will print `false`
```

The crash:
```
Caused by: java.lang.IllegalStateException: pending composition has not been applied
	at androidx.compose.runtime.CompositionImpl.drainPendingModificationsForCompositionLocked(Composition.kt:448)
	at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:635)
	at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:793)
	at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:106)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:460)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:426)
	at androidx.compose.desktop.examples.example1.TestClock.withFrameNanos(Main.jvm.kt:85)
	at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2.invokeSuspend(Recomposer.kt:426)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
	at java.base/java.lang.Thread.run(Thread.java:853)
```

The reproducer:
```
import androidx.compose.runtime.AbstractApplier
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MonotonicFrameClock
import androidx.compose.runtime.currentRecomposeScope
import androidx.compose.runtime.rememberCompositionContext
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.withRunningRecomposer
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield
import java.util.concurrent.Executors.newSingleThreadExecutor
import kotlin.random.Random

fun main() {
    runBlocking(newSingleThreadExecutor().asCoroutineDispatcher() + TestClock) {
        repeat(300) { num ->
            println("test $num")

            withRunningRecomposer {
                val composition = Composition(TestApplier, it)
                composition.setContent {
                    val scope = currentRecomposeScope
                    Test(Random.nextInt())
                    Test(Random.nextInt())
                    LaunchedEffect(Unit) {
                        scope.invalidate()
                    }
                }
                yield()
                yield()
            }
        }
    }
}

@composable
fun Test(test: Int) {
    val latestContent = rememberUpdatedState(test)
    latestContent.value

    val parent = rememberCompositionContext()
    LaunchedEffect(Unit) {
        val composition1 = Composition(TestApplier, parent)
        composition1.setContent {
            latestContent.value
        }
        val composition2 = Composition(TestApplier, parent)
        composition2.setContent {
            latestContent.value
        }
    }
}

internal object TestApplier: AbstractApplier<Unit>(Unit) {
    override fun insertTopDown(index: Int, instance: Unit) = Unit
    override fun insertBottomUp(index: Int, instance: Unit) = Unit
    override fun remove(index: Int, count: Int) = Unit
    override fun move(from: Int, to: Int, count: Int) = Unit
    override fun onClear() = Unit
}

object TestClock : MonotonicFrameClock {
    override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R {
        return onFrame(System.nanoTime())
    }
}
```

java --version:
```
openjdk 15.0.2 2021-01-19
OpenJDK Runtime Environment AdoptOpenJDK (build 15.0.2+7)
Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.24.0, JRE 15 Linux amd64-64-Bit Compressed References 20210121_172 (JIT enabled, AOT enabled)
OpenJ9   - 345e1b09e
OMR      - 741e94ea8
JCL      - 863b523566 based on jdk-15.0.2+7)
```
OS: Ubuntu 20.0.4

Test: manual (see the reproducer)
Test: ./gradlew :compose:runtime:runtime:test (without the fix we have failing Identity*Test on this JDK)
@igordmn
Copy link
Collaborator Author

igordmn commented Feb 1, 2022

(adding this information for history)

The reproducer:

import androidx.compose.runtime.AbstractApplier
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composition
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MonotonicFrameClock
import androidx.compose.runtime.currentRecomposeScope
import androidx.compose.runtime.rememberCompositionContext
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.withRunningRecomposer
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield
import java.util.concurrent.Executors.newSingleThreadExecutor
import kotlin.random.Random

fun main() {
    runBlocking(newSingleThreadExecutor().asCoroutineDispatcher() + TestClock) {
        repeat(300) { num ->
            println("test $num")

            withRunningRecomposer {
                val composition = Composition(TestApplier, it)
                composition.setContent {
                    val scope = currentRecomposeScope
                    Test(Random.nextInt())
                    Test(Random.nextInt())
                    LaunchedEffect(Unit) {
                        scope.invalidate()
                    }
                }
                yield()
                yield()
            }
        }
    }
}

@Composable
fun Test(test: Int) {
    val latestContent = rememberUpdatedState(test)
    latestContent.value

    val parent = rememberCompositionContext()
    LaunchedEffect(Unit) {
        val composition1 = Composition(TestApplier, parent)
        composition1.setContent {
            latestContent.value
        }
        val composition2 = Composition(TestApplier, parent)
        composition2.setContent {
            latestContent.value
        }
    }
}

internal object TestApplier: AbstractApplier<Unit>(Unit) {
    override fun insertTopDown(index: Int, instance: Unit) = Unit
    override fun insertBottomUp(index: Int, instance: Unit) = Unit
    override fun remove(index: Int, count: Int) = Unit
    override fun move(from: Int, to: Int, count: Int) = Unit
    override fun onClear() = Unit
}

object TestClock : MonotonicFrameClock {
    override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R {
        return onFrame(System.nanoTime())
    }
}

Reproduces only on Eclipse OpenJ9, Ubuntu:

openjdk 15.0.2 2021-01-19
OpenJDK Runtime Environment AdoptOpenJDK (build 15.0.2+7)
Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.24.0, JRE 15 Linux amd64-64-Bit Compressed References 20210121_172 (JIT enabled, AOT enabled)
OpenJ9   - 345e1b09e
OMR      - 741e94ea8
JCL      - 863b523566 based on jdk-15.0.2+7)

The issue was because identityHashCode on OpenJ9 returns negative values (issue)

copybara-service bot pushed a commit to androidx/androidx that referenced this issue Feb 1, 2022
Fixes JetBrains/compose-multiplatform#991

Eclipse OpenJ9 can return negative hashcodes (at least 15.0.2):
-1366493741
192407236

when we compare them, we can catch an overflow:
`-1366493741 - 1924072363` will be more than 0

That leads to wrong behaviour of IdentityArraySet, and so of Recomposer:
```
alreadyComposed.add(composition)
println(alreadyComposed.contains(composition)) // will print `false`
```

Test: manual (see the reproducer in JetBrains/compose-multiplatform#991 (comment))
Test: ./gradlew :compose:runtime:runtime:test (without the fix we have failing Identity*Test on this JDK)
Change-Id: Iedfe50d70121a88b16171b15e089fcd8de2e99c9
@zacharee
Copy link

zacharee commented Dec 7, 2023

This seems to have come back with the iOS alpha. Could it be a similar issue?

This was recorded on iOS 17.1.2 on an iPhone 15 Pro Max. I believe the Compose version was 1.6.0-dev1296.

androidx.compose.runtime.ComposeRuntimeError: Compose Runtime internal error. Unexpected or incorrect use of the Compose internal runtime API (pending composition has not been applied). Please report to Google or use https://goo.gle/compose-feedback

0  HINT Control +0xd1dec    kfun:kotlin.Throwable#<init>(){} (Throwable.kt:32:21)
1  HINT Control +0xd1d98    kfun:androidx.compose.runtime#composeRuntimeError(kotlin.String){}kotlin.Nothing (Composer.kt:4558:11)
2  HINT Control +0xe3a54    kfun:androidx.compose.runtime.CompositionImpl.drainPendingModificationsForCompositionLocked#internal (Composition.kt:548:17)
3  HINT Control +0xe3e8c    <inlined-out:<anonymous>> (Composition.kt:586:17)
4  HINT Control +0xf0f98    kfun:androidx.compose.runtime.ControlledComposition#composeContent(kotlin.Function2<androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>){}-trampoline (Composition.kt:112:5)
5  HINT Control +0xe36b0    kfun:androidx.compose.runtime.CompositionContext#composeInitial(androidx.compose.runtime.ControlledComposition;kotlin.Function2<androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>){}-trampoline (CompositionContext.kt:46:23)
6  HINT Control +0x227c10   <inlined-out:<anonymous>> (SubcomposeLayout.kt:721:17)
7  HINT Control +0x6740f0   kfun:androidx.compose.ui.layout.SubcomposeMeasureScope#subcompose(kotlin.Any?;kotlin.Function2<androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>){}kotlin.collections.List<androidx.compose.ui.layout.Measurable>-trampoline (SubcomposeLayout.kt:369:5)
8  HINT Control +0x3453a4   kfun:androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScopeImpl#measure(kotlin.Int;androidx.compose.ui.unit.Constraints){}kotlin.collections.List<androidx.compose.ui.layout.Placeable> (LazyLayoutMeasureScope.kt:121:54)
9  HINT Control +0x6813ec   kfun:androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope#measure(kotlin.Int;androidx.compose.ui.unit.Constraints){}kotlin.collections.List<androidx.compose.ui.layout.Placeable>-trampoline (LazyLayoutMeasureScope.kt:58:5)
10 HINT Control +0x350fa8   kfun:androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridMeasureProvider#getAndMeasure(kotlin.Int;androidx.compose.foundation.lazy.staggeredgrid.SpanRange){}androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridMeasuredItem (LazyStaggeredGridMeasure.kt:1033:39)
11 HINT Control +0x35242c   <inlined-out:<anonymous>> (LazyStaggeredGridMeasure.kt:507:53)
12 HINT Control +0x355e88   kfun:androidx.compose.foundation.lazy.staggeredgrid#measureStaggeredGrid__at__androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState;kotlin.collections.List<kotlin.Int>;androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemProvider;androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridSlots;androidx.compose.ui.unit.Constraints;kotlin.Boolean;kotlin.Boolean;androidx.compose.ui.unit.IntOffset;kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Int){}androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridMeasureResult (LazyStaggeredGridMeasure.kt:166:20)
13 HINT Control +0x643744   kfun:kotlin.Function2#invoke(1:0;1:1){}1:2-trampoline ([K][Suspend]Functions:1:1)
14 HINT Control +0x340204   <inlined-out:<anonymous>> (LazyLayout.kt:87:25)
15 HINT Control +0x643744   kfun:kotlin.Function2#invoke(1:0;1:1){}1:2-trampoline ([K][Suspend]Functions:1:1)
16 HINT Control +0x228bc0   kfun:androidx.compose.ui.layout.LayoutNodeSubcompositionsState.object-1.measure#internal (SubcomposeLayout.kt:866:40)
17 HINT Control +0x673194   kfun:androidx.compose.ui.layout.MeasurePolicy#measure__at__androidx.compose.ui.layout.MeasureScope(kotlin.collections.List<androidx.compose.ui.layout.Measurable>;androidx.compose.ui.unit.Constraints){}androidx.compose.ui.layout.MeasureResult-trampoline (MeasurePolicy.kt:88:5)
18 HINT Control +0x236aa0   <inlined-out:<anonymous>> (InnerNodeCoordinator.kt:126:13)
19 HINT Control +0x66c33c   kfun:androidx.compose.ui.layout.Measurable#measure(androidx.compose.ui.unit.Constraints){}androidx.compose.ui.layout.Placeable-trampoline (Measurable.kt:30:5)
20 HINT Control +0x2e11a4   kfun:androidx.compose.foundation.layout.OffsetPxNode.measure#internal (Offset.kt:245:36)
21 HINT Control +0x6751a0   kfun:androidx.compose.ui.node.LayoutModifierNode#measure__at__androidx.compose.ui.layout.MeasureScope(androidx.compose.ui.layout.Measurable;androidx.compose.ui.unit.Constraints){}androidx.compose.ui.layout.MeasureResult-trampoline (LayoutModifierNode.kt:64:5)
22 HINT Control +0x23a280   <inlined-out:<anonymous>> (LayoutModifierNodeCoordinator.kt:116:21)
23 HINT Control +0x66c33c   kfun:androidx.compose.ui.layout.Measurable#measure(androidx.compose.ui.unit.Constraints){}androidx.compose.ui.layout.Placeable-trampoline (Measurable.kt:30:5)
24 HINT Control +0x202a1c   kfun:androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure#internal (GraphicsLayerModifier.kt:646:36)
25 HINT Control +0x6751a0   kfun:androidx.compose.ui.node.LayoutModifierNode#measure__at__androidx.compose.ui.layout.MeasureScope(androidx.compose.ui.layout.Measurable;androidx.compose.ui.unit.Constraints){}androidx.compose.ui.layout.MeasureResult-trampoline (LayoutModifierNode.kt:64:5)
26 HINT Control +0x23a280   <inlined-out:<anonymous>> (LayoutModifierNodeCoordinator.kt:116:21)
27 HINT Control +0x24ccfc   kfun:androidx.compose.ui.node.NodeCoordinator#measure(androidx.compose.ui.unit.Constraints){}androidx.compose.ui.layout.Placeable-trampoline (NodeCoordinator.kt:28:18)
28 HINT Control +0x6414e0   kfun:kotlin.Function0#invoke(){}1:0-trampoline ([K][Suspend]Functions:1:1)
29 HINT Control +0x12197c   <inlined-out:enter> (Snapshot.kt:132:20)
30 HINT Control +0x13803c   <inlined-out:<anonymous>> (SnapshotStateObserver.kt:471:26)
31 HINT Control +0x248834   kfun:androidx.compose.ui.node.OwnerSnapshotObserver#observeReads(0:0;kotlin.Function1<0:0,kotlin.Unit>;kotlin.Function0<kotlin.Unit>){0§<androidx.compose.ui.node.OwnerScope>} (OwnerSnapshotObserver.kt:133:18)
32 HINT Control +0x2421f4   kfun:androidx.compose.ui.node.LayoutNode#remeasure(androidx.compose.ui.unit.Constraints?){}kotlin.Boolean (LayoutNode.kt:1140:33)
33 HINT Control +0x242294   kfun:androidx.compose.ui.node.LayoutNode#remeasure$default(androidx.compose.ui.unit.Constraints?;kotlin.Int){}kotlin.Boolean (LayoutNode.kt:1131:14)
34 HINT Control +0x24f7ec   kfun:androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure#internal (MeasureAndLayoutDelegate.kt:323:24)
35 HINT Control +0x24ff30   kfun:androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded#internal (MeasureAndLayoutDelegate.kt:458:31)
36 HINT Control +0x276b14   <inlined-out:<anonymous>> (MeasureAndLayoutDelegate.kt:344:39)
37 HINT Control +0x284338   kfun:androidx.compose.ui.node.RootNodeOwner#measureAndLayout(){} (RootNodeOwner.skiko.kt:179:15)
38 HINT Control +0x27f260   kfun:androidx.compose.ui.scene.BaseComposeScene#measureAndLayout(){}-trampoline (BaseComposeScene.skiko.kt:220:24)
39 HINT Control +0x2a9478   kfun:androidx.compose.ui.scene.ComposeScene#render(androidx.compose.ui.graphics.Canvas;kotlin.Long){}-trampoline (ComposeScene.skiko.kt:155:5)
40 HINT Control +0x2bbf14   kfun:androidx.compose.ui.window.SkikoUIViewDelegate#render(org.jetbrains.skia.Canvas;kotlin.Double){}-trampoline (SkikoUIView.kt:55:5)
41 HINT Control +0x2b2660   kfun:androidx.compose.ui.window.MetalRedrawerCallbacks#render(org.jetbrains.skia.Canvas;kotlin.Double){}-trampoline (MetalRedrawer.kt:161:5)
42 HINT Control +0x2b389c   <inlined-out:<anonymous>> (MetalRedrawer.kt:251:17)
43 HINT Control +0x6414e0   kfun:kotlin.Function0#invoke(){}1:0-trampoline ([K][Suspend]Functions:1:1)
44 HINT Control +0x2b5d40   kfun:androidx.compose.ui.window.DisplayLinkProxy.handleDisplayLinkTick#internal (MetalRedrawer.kt:457:9)
45 QuartzCore +0x2fa10      CA::Display::DisplayLinkItem::dispatch_(CA::SignPost::Interval<(CA::SignPost::CAEventCode)835322056>&)
46 QuartzCore +0x32bf8      CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long)
47 QuartzCore +0x32704      CA::Display::DisplayLink::callback(_CADisplayTimer*, unsigned long long, unsigned long long, unsigned long long, bool, void*)
48 QuartzCore +0xb1918      CA::Display::DisplayLink::dispatch_deferred_display_links(unsigned int)
49 UIKitCore +0xaa488       __UIUpdateSequenceRun
50 UIKitCore +0xa9b78       _schedulerStepScheduledMainSection
51 UIKitCore +0xa9c34       _runloopSourceCallback
52 CoreFoundation +0x37318  ___CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
53 CoreFoundation +0x36594  ___CFRunLoopDoSource0
54 CoreFoundation +0x34d48  ___CFRunLoopDoSources0
55 CoreFoundation +0x33a84  ___CFRunLoopRun
56 CoreFoundation +0x33664  _CFRunLoopRunSpecific
57 GraphicsServices +0x35e8 _GSEventRunModal
58 UIKitCore +0x22c2b0      -[UIApplication _run]
59 UIKitCore +0x22b8ec      _UIApplicationMain
60 SwiftUI +0x11620f8       0x18cb820f8 (0x18cb8204c + 172)
61 SwiftUI +0x1161f3c       0x18cb81f3c (0x18cb81ea8 + 148)
62 SwiftUI +0xdd3864        0x18c7f3864 (0x18c7f37e8 + 124)
63 HINT Control +0x5948     static iOSApp.$main() (iOSApp.swift)
64 dyld +0x5dc8             start

@m-sasha
Copy link
Member

m-sasha commented Dec 7, 2023

@zacharee Can you post a short snipped where this is reproduced?

@zacharee
Copy link

zacharee commented Dec 7, 2023

This is from a crash report on Bugsnag, so I don't know exactly how to reproduce it. This is the code, though: https://github.com/zacharee/ArcadyanKVD21Control/blob/727ce98524e71a056098dd9af67d5c278a835bf0/common/src/commonMain/kotlin/dev/zwander/common/components/PageGrid.kt.

@m-sasha
Copy link
Member

m-sasha commented Dec 8, 2023

Unfortunately there's not much we can do without a reproducer here.

@okushnikov
Copy link
Collaborator

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

@JetBrains JetBrains locked and limited conversation to collaborators Dec 18, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants