Skip to content
New issue

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

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

Already on GitHub? # 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

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

No branches or pull requests

3 participants