Skip to content

Conversation

@tak8997
Copy link
Contributor

@tak8997 tak8997 commented Nov 12, 2025

Summary

This PR introduces a mechanism to prevent multiple parallel network requests for the same URL in Coil.

Details

  • Uses HashMap and CompletableDeferred to track in-flight requests.
  • If a request for the same URL is already in progress, subsequent requests will wait for the first to complete and reuse its result.

Motivation

Comment on lines 245 to 265
private fun generateRequestKey(url: String, options: Options): String {
return buildString {
append(url)
append('|')
append(options.httpMethod)
append('|')
append(options.httpBody?.hashCode() ?: 0)
append('|')
val headers = options.httpHeaders.asMap()
.flatMap { (name, values) -> values.map { value -> name to value } }
.sortedBy { it.first }
.joinToString(separator = ",") { "${it.first}:${it.second}" }
append(headers)
append('|')
append(options.networkCachePolicy)
append('|')
append(options.diskCachePolicy)
append('|')
append(options.diskCacheKey)
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would appreciate some advice on how to make a request key

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, I think we want to make it configurable. We should make it an interface argument to NetworkFetcher so users can provide their own. By default I think our request key should only be url.

@tak8997
Copy link
Contributor Author

tak8997 commented Nov 20, 2025

@colinrtwhite Could you please check this PR?

}
}
if (deferred !== newDeferred) {
return deferred.await()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will propagate cancellation so if the request that started first is cancelled, all observers will also be cancelled. Ideally if we have >= 1 requests still awaiting and not cancelled, we shouldn't cancel the underlying network operation and let it complete.

return deferred.await()
}

var snapshot = readFromDiskCache()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this and everything below it into a separate method. That way we can call that method and wrap it with deferred.complete etc. vs. completing the deferred before each return.

private val diskCache: Lazy<DiskCache?>,
private val cacheStrategy: Lazy<CacheStrategy>,
private val connectivityChecker: ConnectivityChecker,
private val pendingRequests: HashMap<String, CompletableDeferred<SourceFetchResult>>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably refactor pendingRequests + the mutex into a separate class that hides these as an implementation detail. It would be similar to how DeDupeInFlightRequestStrategy has both a non-synchronized implementation and a synchronized implementation in this PR: #2995


public final class coil3/network/NetworkFetcher : coil3/fetch/Fetcher {
public fun <init> (Ljava/lang/String;Lcoil3/request/Options;Lkotlin/Lazy;Lkotlin/Lazy;Lkotlin/Lazy;Lcoil3/network/ConnectivityChecker;)V
public fun <init> (Ljava/lang/String;Lcoil3/request/Options;Lkotlin/Lazy;Lkotlin/Lazy;Lkotlin/Lazy;Lcoil3/network/ConnectivityChecker;Ljava/util/HashMap;Lkotlinx/coroutines/sync/Mutex;)V
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding constructor arguments to NetworkFetcher breaks binary compatibility so we'll have to keep the old constructors as secondary constructors and add @Deprecated("Kept for binary compatibility.", level = DeprecationLevel.HIDDEN) to them.

renovate bot and others added 20 commits December 24, 2025 18:27
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…gin to v1.9.3 (coil-kt#3221)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
coil-kt#3225)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…in to v0.35.0 (coil-kt#3227)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
… v0.8.2 (coil-kt#3243)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* chore(deps): update plugin screenshot to v0.0.1-alpha12

* Use JDK 21.

* Suppress warning.

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Colin White <colin@colinwhite.me>
…v1.4.2 (coil-kt#3255)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
coil-kt#3258)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* fix(deps): update kotlin monorepo to v2.3.0

* Fix build issues.

* Fix warning.

* Fix warning.

* Update Poko.

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Colin White <colin@colinwhite.me>
…t. (coil-kt#3170)

* Migrate to the Kotlin plugin's binary compatibility validation support.

# Conflicts:
#	AGENTS.md
#	gradle/libs.versions.toml

* Update API.
* Remove the Okio polyfill JS workaround.

It only removes a warning on certain variants and one of the APIs it relies on is deprecated in Kotlin 2.3.0.

# Conflicts:
#	build.gradle.kts

* Update yarn.lock

* Update yarn.lock
@tak8997 tak8997 closed this Jan 13, 2026
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

Successfully merging this pull request may close these issues.

4 participants