Sports widget: throttle World Cup matches API and add Nimbus-controlled interval with mock-server bypass
Categories
(Firefox for Android :: Homepage, enhancement)
Tracking
()
People
(Reporter: skhan, Assigned: skhan)
References
Details
(Whiteboard: [fxdroid][group3][world-cup-phase2])
Attachments
(3 external files)
Every dispatch site that triggers a sports-widget refresh (HomeFragment.onResume, refresh button, debug-tool toggles, post-CountriesSelected when cache is empty) hits the Merino WCS endpoint with no rate limit and no concurrency guard.
This lets a user spam the refresh button or swipe back and forth from Home to fire dozens of requests per second, all of which return the same cached payload (Merino caches upstream data for ~3 min).
Fix in SportsWidgetMiddleware.fetchAndBuild:
-
In-flight dedup: track the launched coroutine in a Job? field. If a fetch is in flight when a new one is requested, drop the new request silently.
-
Min-interval throttle: 60 seconds by default. Stamp lastFetchAtMs on attempt (not on success) so failures also respect the throttle and can't be hammered. Worst-case live latency becomes ~4 min (client 60s + upstream 3 min cache) instead of unbounded.
-
Nimbus-controlled interval:
homepage-sports-widget.fetch-throttle-seconds(Int, default 60). Lets the team lower the throttle remotely if the upstream Merino TTL is reduced. Exposed in Settings.kt as sportsWidgetFetchThrottleMs and wired into the middleware at construction time. -
Mock-server bypass: when settings.useMockWorldCupServer is true (debug drawer toggle), the throttle is skipped entirely. QA can drive state changes via refresh button, onResume, and the Apply session button without waiting on the timer. The in-flight dedup is preserved even in bypass mode -- never want two concurrent calls to the same endpoint, mock or not. The bypass is evaluated per-call via a lambda so the flag can be flipped at runtime without restarting the app.
Updated•5 months ago
|
Every dispatch site that triggers a sports-widget refresh (HomeFragment.onResume,
refresh button, debug-tool toggles, post-CountriesSelected when cache is empty)
previously hit the Merino WCS endpoint with no rate limit and no concurrency
guard. A user mashing the refresh button or rapidly swiping between Home and
another tab could fire dozens of requests per second, all of which return the
same cached payload (Merino caches upstream data for ~3 min).
Guard fetchAndBuild with two checks:
-
In-flight dedup. Track the launched coroutine in a Job? field; drop any new
dispatch while it's still active. Costs nothing when there's no contention
and prevents concurrent round-trips when there is. -
Min-interval throttle, default 60 s. Stamp lastFetchAtMs on attempt (not on
success) so a failing endpoint can't be hammered by tapping retry. Worst-case
live-score latency becomes ~4 min (client 60 s + upstream 3 min cache)
instead of unbounded.
The interval is Nimbus-controlled via the new
homepage-sports-widget.fetch-throttle-seconds variable (Int, default 60),
exposed as Settings.sportsWidgetFetchThrottleMs and read at middleware
construction. Lets the team lower the throttle remotely if upstream Merino
TTL is reduced.
The throttle is bypassed when settings.useMockWorldCupServer is true so QA
can drive state changes via refresh, Home onResume, and the debug drawer's
"Apply session" button without waiting on the timer. The bypass is a lambda
evaluated per dispatch, not snapshotted at construction, so toggling the
debug drawer takes effect immediately. The in-flight dedup is preserved even
in bypass mode — concurrent calls to the same endpoint are wasteful regardless
of whether they hit mock or production.
Tests cover both guards across the default, custom-interval, failure, and
bypass paths.
Updated•5 months ago
|
Comment 3•5 months ago
|
||
| bugherder | ||
Every dispatch site that triggers a sports-widget refresh (HomeFragment.onResume,
refresh button, debug-tool toggles, post-CountriesSelected when cache is empty)
previously hit the Merino WCS endpoint with no rate limit and no concurrency
guard. A user mashing the refresh button or rapidly swiping between Home and
another tab could fire dozens of requests per second, all of which return the
same cached payload (Merino caches upstream data for ~3 min).
Guard fetchAndBuild with two checks:
-
In-flight dedup. Track the launched coroutine in a Job? field; drop any new
dispatch while it's still active. Costs nothing when there's no contention
and prevents concurrent round-trips when there is. -
Min-interval throttle, default 60 s. Stamp lastFetchAtMs on attempt (not on
success) so a failing endpoint can't be hammered by tapping retry. Worst-case
live-score latency becomes ~4 min (client 60 s + upstream 3 min cache)
instead of unbounded.
The interval is Nimbus-controlled via the new
homepage-sports-widget.fetch-throttle-seconds variable (Int, default 60),
exposed as Settings.sportsWidgetFetchThrottleMs and read at middleware
construction. Lets the team lower the throttle remotely if upstream Merino
TTL is reduced.
The throttle is bypassed when settings.useMockWorldCupServer is true so QA
can drive state changes via refresh, Home onResume, and the debug drawer's
"Apply session" button without waiting on the timer. The bypass is a lambda
evaluated per dispatch, not snapshotted at construction, so toggling the
debug drawer takes effect immediately. The in-flight dedup is preserved even
in bypass mode — concurrent calls to the same endpoint are wasteful regardless
of whether they hit mock or production.
Tests cover both guards across the default, custom-interval, failure, and
bypass paths.
Original Revision: https://phabricator.services.mozilla.com/D304151
Updated•5 months ago
|
Every dispatch site that triggers a sports-widget refresh (HomeFragment.onResume,
refresh button, debug-tool toggles, post-CountriesSelected when cache is empty)
previously hit the Merino WCS endpoint with no rate limit and no concurrency
guard. A user mashing the refresh button or rapidly swiping between Home and
another tab could fire dozens of requests per second, all of which return the
same cached payload (Merino caches upstream data for ~3 min).
Guard fetchAndBuild with two checks:
-
In-flight dedup. Track the launched coroutine in a Job? field; drop any new
dispatch while it's still active. Costs nothing when there's no contention
and prevents concurrent round-trips when there is. -
Min-interval throttle, default 60 s. Stamp lastFetchAtMs on attempt (not on
success) so a failing endpoint can't be hammered by tapping retry. Worst-case
live-score latency becomes ~4 min (client 60 s + upstream 3 min cache)
instead of unbounded.
The interval is Nimbus-controlled via the new
homepage-sports-widget.fetch-throttle-seconds variable (Int, default 60),
exposed as Settings.sportsWidgetFetchThrottleMs and read at middleware
construction. Lets the team lower the throttle remotely if upstream Merino
TTL is reduced.
The throttle is bypassed when settings.useMockWorldCupServer is true so QA
can drive state changes via refresh, Home onResume, and the debug drawer's
"Apply session" button without waiting on the timer. The bypass is a lambda
evaluated per dispatch, not snapshotted at construction, so toggling the
debug drawer takes effect immediately. The in-flight dedup is preserved even
in bypass mode — concurrent calls to the same endpoint are wasteful regardless
of whether they hit mock or production.
Tests cover both guards across the default, custom-interval, failure, and
bypass paths.
Original Revision: https://phabricator.services.mozilla.com/D304151
Updated•5 months ago
|
Updated•5 months ago
|
Updated•5 months ago
|
Updated•5 months ago
|
Updated•5 months ago
|
Updated•5 months ago
|
Description
•