Bugfix FXIOS-15847 [Homepage] Serve last known sponsored tiles while revalidating in the background#34695
Conversation
…revalidating in the background When the ads client cache expires (every 30 minutes), requestTileAds performs a blocking network request. The homepage top sites section waits on that request (up to the 3s timeout) before presenting any shortcuts, and a timed-out request drops sponsored tiles from that homepage build entirely while still tying up a Swift Concurrency cooperative thread until the request finishes. UnifiedAdsProvider now keeps the last successfully fetched tiles in memory and serves them immediately for up to an hour while a refresh runs on a dedicated dispatch queue. Results that arrive after the caller stopped waiting are cached for the next homepage build, and concurrent revalidations are coalesced.
lmarceau
left a comment
There was a problem hiding this comment.
Throwing in some thoughts in there 👀 Let me know what you think Roux & Matt!
| } | ||
|
|
||
| final class UnifiedAdsProvider: UnifiedAdsProviderInterface, Sendable { | ||
| final class UnifiedAdsProvider: UnifiedAdsProviderInterface, @unchecked Sendable { |
There was a problem hiding this comment.
UnifiedAdsProvider is currently using completion handlers since it was introduced at the time as a "let's throw in something quickly to support ads", but it might be worth rethinking it as a whole so we can avoid introducing locking mechanism and support modern concurrency instead.
| @@ -43,10 +61,15 @@ final class UnifiedAdsProvider: UnifiedAdsProviderInterface, Sendable { | |||
|
|
|||
| init( | |||
| adsClientFactory: MozAdsClientFactory = DefaultMozAdsClientFactory(), | |||
There was a problem hiding this comment.
TBH this whole situation looks like we should have this living under the MozAdsClient instead of the Client level UnifiedAdsProvider, what do you think Matt/Roux?
There was a problem hiding this comment.
Yeah, I'm looking into this right now. This whole thing might need a better refactor as it was meant to be temporary.
…ait in UnifiedAdsProvider Replaces the completion-handler + NSLock design flagged in review with a modern-concurrency one: UnifiedAdsProvider is now an actor with an async fetchTiles, so the in-memory tile cache is protected by actor isolation instead of a lock and @unchecked Sendable is narrowed to a small box around the thread-safe Rust ads client. The blocking requestTileAds FFI call still runs on a dedicated queue so it never occupies a cooperative-pool thread. Adapts the single caller (TopSitesManager) to await the async API while keeping the return-early-on-timeout, cache-late-results behavior, and updates MockUnifiedAdsProvider and the provider tests to the async API.
|
@lmarceau @adudenamedruby Thanks both 🙏 (replying here since I couldn't reply in-thread) I've reworked I kept it deliberately self-contained and easy to delete or fold into a |
📜 Tickets
Jira ticket
Github issue
💡 Description
This implements the sponsored-shortcuts part of #33899 — "Use stale sponsored shortcuts … while refreshing the cache in the background" — scoped entirely to
UnifiedAdsProvider, so it does not change how or when the homepage renders.Why the shortcuts section stalls on slow networks
MozAdsClient.requestTileAdsis a blocking FFI call, and the ads client's HTTP cache hard-expires entries (default 30 minutes):delete_expired_entries()runs before every lookup, so once the TTL passes the client must go to the network — the previous response is unreadable at that point.TopSitesMiddleware.getTopSitesDataAndUpdateStatedispatches a singleretrievedUpdatedSitesaction that awaits bothgetOtherSites()(local, milliseconds) andfetchSponsoredSites(). With an expired ad cache on a slow network, the entire shortcuts section (pinned + history + Google tile — all local data) waits on the sponsored request until the 3-second timeout added in FXIOS-16077.fetchTilesran it synchronously on the calling task's thread (the same class of problem as FXIOS-16156 / FXIOS-16307).URLCachewithinmaxCacheAge(30 min) without touching the network. That ability was lost in the Rust ads client migration (FXIOS-14580; legacy path removed with the flag in FXIOS-15992) — thetimestampparameter onfetchTileshas been vestigial since then.What this PR changes (all inside
UnifiedAdsProvider)requestTileAdscall now runs on a dedicated dispatch queue instead of the caller's thread, mirroringDefaultUnifiedAdsCallbackTelemetry(FXIOS-15787) and the merino WC fetcher fix (FXIOS-16156).maxTileStaleness(1 hour) old, completes immediately with them and revalidates in the background. After the first successful fetch of a session, a homepage build never waits on the ad network again. Concurrent revalidations are coalesced.Scope / behavior notes
initialize/didBecomeActive/topSitesUpdated), so this does not introduce the mid-view content shifting that #33899 ⁃ Improve homepage loading on slow networks when sponsored shortcuts or stories data needs refresh #33899 flags as needing design input.Testing
UnifiedAdsProviderTestscovers every branch of the new behavior deterministically (no sleeps, timestamps injected): serving within / at / beyond the staleness bound, revalidation updating the cache, failed revalidation keeping the previous tiles, empty responses being cached, revalidation coalescing while one is in flight, and a late-arriving result being served on the next fetch. Existing tests were updated for the queue-based completion with explicit expectations. SwiftLint passes on the changed files; unit tests run in CI.🎥 Demos
No visual changes — the shortcuts section simply appears without waiting on the ads request when tiles were fetched within the last hour.
📝 Checklist