compose-multiplatform: Skia crash when calling Image.makeFromEncoded(bytes).asImageAsset()

Unfortunately i am not able to reproduce it reliable. But when i am loading many images and display it some times it crashes. To reproduce it execute the following code and scroll fast up and down. Sometimes it happens after just 10-20 images loadings. Sometimes it takes much longer. Also it is not every time necessary to scroll fast. I also tried to load the data into bytearrays first and than show alle images instantly without loading but it will still not crash reliable. Please see the crash details in this file: hs_err_pid15251.log Here is the code to reproduce the bug:

import androidx.compose.desktop.Window
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumnFor
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageAsset
import androidx.compose.ui.graphics.asImageAsset
import androidx.compose.ui.unit.IntSize
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.skija.Image
import java.net.URL

data class RemoteImage(val url: String, val width: Int, val height: Int)

suspend fun loadImageUrl(url: String) = withContext(Dispatchers.IO) {
    val bytes = URL(url).openStream().use { it.readAllBytes() }
    log("Decode bytes: ${bytes.size}")
    Image.makeFromEncoded(bytes).asImageAsset()
}

@Composable
fun loadImageAsyn(url: String): ImageAsset? {
    var state: ImageAsset? by remember(url) { mutableStateOf(null) }
    LaunchedEffect(url) {
        state = try {
            loadImageUrl(url)
        } catch (err: Exception) {
            println("Unable to load image $url: ${err.message}")
            null
        }
    }
    return state
}

fun main() {
    val width = 1000
    val height = 600
    val remoteImageList = (0..500).map {
        RemoteImage(
            url = "https://picsum.photos/id/$it/$width/$height",
            width = width,
            height = height
        )
    }

    Window(
        title = "Image Browser",
        size = IntSize(200,800),
        centered = true,
    ) {
        LazyColumnFor(remoteImageList) { item ->
            val image = loadImageAsyn(item.url)
            val aspectRatio = item.width.toFloat() / item.height.toFloat()
            Box(Modifier.aspectRatio(aspectRatio).fillMaxWidth()) {
                image?.let {
                    Image(modifier = Modifier.fillMaxSize(), asset = it)
                }
            }
        }
    }
}

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 23 (14 by maintainers)

Commits related to this issue

Most upvoted comments

Ok i slightly modified the reproducer from Igor. Now it crashes reliable at least on my linux system. Updated https://gitlab.com/compose1/minimal-compose-desktop/-/tree/skia_crash

import androidx.compose.desktop.Window
import androidx.compose.ui.graphics.imageFromResource
import java.util.concurrent.Executors

val threadPool = Executors.newFixedThreadPool(10)

fun main() {

    repeat(1000) {
        threadPool.execute {
            imageFromResource("test.jpg")
        }
    }

    Window() {
    }
}

@timo-drick could you please recheck with 0.0.0-unmerged-build21?

Fixed in https://github.com/JetBrains/skiko/commit/b0dc2e019ded28feef9326da3c68a6df02d9e8da. Once move Compose to Skiko 0.1.20 - problem shall go away.

@timo-drick thanks a lot for help with getting that reproduced and fixed!

With concurrency of 100 threads - we also can crash on macOS.

With following reproducer indeed could see a crash on Linux system:

import androidx.compose.desktop.Window
import androidx.compose.material.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.graphics.imageFromResource
import java.util.concurrent.Executors
import kotlin.concurrent.thread

val threadPool = Executors.newFixedThreadPool(10)

fun main() {
    val state = mutableStateOf(0)
    imageFromResource("test.jpg")
    thread {
        repeat(10000) {
            threadPool.execute {
                imageFromResource("test.jpg")
                state.value++
            }
        }
    }

    Window {
        Text(state.value.toString())
    }
}

I have stable reproducer:

import androidx.compose.desktop.Window
import androidx.compose.ui.graphics.imageFromResource

fun main() {
    repeat(1000) {
        Thread {
            imageFromResource("androidx/compose/desktop/example/circus.jpg")
        }.start()
    }

    Window {
    }
}