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

Coil returned an image that is missing from its disk cache #106

Open
latsson opened this issue Oct 23, 2024 · 5 comments
Open

Coil returned an image that is missing from its disk cache #106

latsson opened this issue Oct 23, 2024 · 5 comments

Comments

@latsson
Copy link

latsson commented Oct 23, 2024

Hey!

Thanks for a really nice library. This is the only one I found in Compose that keeps the quality of the image while zooming.

When rolling this out in a large app we have noticed a bunch of crashes. This seems to happen for all devices on all OS versions. No idea how to reproduce it though.

Fatal Exception: java.lang.IllegalStateException: Coil returned an image that is missing from its disk cache
       at me.saket.telephoto.zoomable.coil.Resolver.toSubSamplingImageSource(CoilImageSource.kt:187)
       at me.saket.telephoto.zoomable.coil.Resolver.access$toSubSamplingImageSource(CoilImageSource.kt)
       at me.saket.telephoto.zoomable.coil.Resolver$toSubSamplingImageSource$1.invokeSuspend(CoilImageSource.kt:12)
       at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
       at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:101)
       at android.os.Handler.handleCallback(Handler.java:958)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loopOnce(Looper.java:230)
       at android.os.Looper.loop(Looper.java:319)
       at android.app.ActivityThread.main(ActivityThread.java:8919)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:578)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103)

My code (inside a HorizontalPager)

val zoomState = rememberZoomableImageState(
    zoomableState = rememberZoomableState(
        zoomSpec = ZoomSpec(maxZoomFactor = 3f),
    ),
)

ZoomableAsyncImage(
    model = mediaItem.url,
    contentDescription = mediaItem.altText,
    state = zoomState,
)

Version used:
telephotoZoomableImageCoil = { module = "me.saket.telephoto:zoomable-image-coil", version = "0.13.0" }

@saket
Copy link
Owner

saket commented Oct 24, 2024

Hmm it's weird that coil is returning a null entry from its disk cache despite reporting a non-null disk cache key.

val snapshot = withContext(Dispatchers.IO) { // IO because openSnapshot() can delete files.
diskCache.openSnapshot(result.diskCacheKey!!)
}
if (snapshot == null) {
return when (result.dataSource) {
DataSource.MEMORY_CACHE -> ImageDeletedOnlyFromDiskCache
else -> error("Coil returned an image that is missing from its disk cache")
}
}

@latsson Are there any other useful breadcrumbs left by your error reporting service?

@saket
Copy link
Owner

saket commented Oct 24, 2024

@colinrtwhite are there any other edge cases related to disk caching that I might be missing?

This is probably another example of why I wish Coil had a public API for downloading images to disk.

@latsson
Copy link
Author

latsson commented Oct 24, 2024

Hmm it's weird that coil is returning a null entry from its disk cache despite reporting a non-null disk cache key.

val snapshot = withContext(Dispatchers.IO) { // IO because openSnapshot() can delete files.
diskCache.openSnapshot(result.diskCacheKey!!)
}
if (snapshot == null) {
return when (result.dataSource) {
DataSource.MEMORY_CACHE -> ImageDeletedOnlyFromDiskCache
else -> error("Coil returned an image that is missing from its disk cache")
}
}

@latsson Are there any other useful breadcrumbs left by your error reporting service?

Sadly nothing other of interest 😐 It's just twice as common as the other 2 I wrote about.

@colinrtwhite
Copy link

@saket I think there's a couple potential cases where result.diskCacheKey could be non-null by the time openSnapshot is called:

  • Another request starts writing to the same cache entry after the image data was written, but before openSnapshot is called. Coil's DiskCache doesn't allow simultaneous reads + writes as it's not possible since it exposes the underlying file path.
  • Android's cache directory gets cleared after the image data was written, but before openSnapshot is called. The system can do this at any time, though I believe it won't delete files with an open source/sink (not 100% on this).

I think what Telefoto is looking for is an atomic API that writes the image data to disk then immediately opens a source for the file. That way we avoid the race condition between writing the image data and calling openSnapshot.

Could you open a feature request on Coil's tracker so we can track? 🙏🏻

@saket
Copy link
Owner

saket commented Nov 2, 2024

Done, thank you! coil-kt/coil#2630

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants