Skip to content

fix: recover from FR24 live-feed backends that return no flights - #111

Merged
JeanExtreme002 merged 3 commits into
mainfrom
fix/live-feed-empty-backend
Jul 29, 2026
Merged

fix: recover from FR24 live-feed backends that return no flights#111
JeanExtreme002 merged 3 commits into
mainfrom
fix/live-feed-empty-backend

Conversation

@JeanExtreme002

Copy link
Copy Markdown
Owner

Problem

The scheduled runs are red in both SDKs, always on the same tests: everything that goes through getFlights() / get_flights() comes back with 0 flights (assert 0 >= 100, IndexError, expected +0 to be above 29), while the airport and airline tests pass.

Example: Node run 29882853857, and Python run 29884728134.

Root cause

Not our bug — FR24 is serving a broken backend.

data-cloud.flightradar24.com/zones/fcgi/feed.js sits behind an AWS load balancer with at least two targets, visible in the x-fr24-fcgi response header:

backend response
frontend-feed-002 ~1500 flight entries, full_count ticking up each call
frontend-feed-001 zero entries, full_count frozen at 22684 for 20+ minutes

The degraded one answers HTTP 200 with a perfectly well-formed envelope — full_count set, stats.total populated — but no flight rows and stats.visible all zeros. Roughly half of all requests landed on it.

Two things turned a 50/50 coin flip into a deterministic failure:

  1. The AWSALB cookie pins the session to a backend (confirmed 8/8 in both directions). Since Session persists cookies, once a test process landed on the broken target, every later call returned [] — which is exactly why all four flight tests failed together.
  2. The degraded payload is structurally identical to a legitimately empty result (say, a bounds box over open ocean). No field in the response distinguishes them.

Fix

getFlights() / get_flights() now treat "zero entries while full_count > 0" as suspect: drop the AWSALB/AWSALBCORS cookies so the balancer re-rolls the target, then re-request, up to 4 retries. A successful response re-pins the session to the healthy backend, so the gamble is paid at most once per client. A genuinely empty query still returns [], just after spending the retry budget.

Shedding stickiness needs a targeted delete — clearCookies() would also drop _frPl, the login cookie, which shares the same jar. Hence the new deleteCookie / delete_cookie on the client (typed in index.d.ts).

Tests

New offline suites in both SDKs (testFeedRetry.js, test_feed_retry.py), 7 cases each, no network, using a client double: retry-then-succeed, bounded give-up, no retry on full_count: 0, no retry when the first response is healthy, caller filters preserved across retries, and the login cookie surviving a stickiness drop. They run in the PR gate, so they keep guarding this while FR24 is healthy.

Verification

  • Node — eslint and tsd clean, 40 offline, integration 46/46 in 6 of 7 runs
  • Python — flake8 and mypy clean, 41 offline, integration 46/46

Out of scope

  • RetryPolicy does not treat HTTP 429 as transient, and it defaults to off (maxAttempts=1). A real gap, but not what broke these runs — left for a separate change.
  • Two one-off failures seen locally (a 429 on clickhandler, and the airline-logo test) came from firing hundreds of requests within a few minutes, not from CI conditions.

FR24 balances zones/fcgi/feed.js across several backends and at least one
of them answers HTTP 200 with a well-formed envelope -- full_count set,
stats.total populated -- but no flight entries and stats.visible zeroed.
Roughly half the requests landed on it, and its full_count stayed frozen
at the same value for over 20 minutes while a healthy backend kept
ticking up.

Two things turned that into a deterministic failure. The AWSALB cookie
pins the session to whichever backend it first hit, so a client that
landed on the degraded one returned an empty flight list for the rest of
its lifetime -- which is why every flight test failed together while the
airport and airline tests passed. And the degraded payload is
structurally identical to a legitimately empty result (a bounds box over
open ocean), so no field in the response can tell the two apart.

getFlights()/get_flights() now treat zero entries with full_count > 0 as
suspect: drop the stickiness cookies so the balancer re-rolls the
backend, then re-request, up to four times. A successful response
re-pins the session to the healthy backend, so the cost is paid at most
once per client. A genuinely empty query still returns an empty list.

Shedding stickiness needs a targeted cookie delete: clearCookies() would
also drop _frPl, the login cookie, which shares the same jar. Hence the
new deleteCookie/delete_cookie on the client.

Covered by new offline tests in both SDKs (retry-then-succeed, bounded
give-up, no retry on full_count 0, no retry when healthy, filters
preserved across retries, login cookie survives), so the PR gate keeps
guarding this while FR24 is healthy.
@JeanExtreme002
JeanExtreme002 force-pushed the fix/live-feed-empty-backend branch from b05a5ef to 4b2c349 Compare July 29, 2026 13:26
@github-actions github-actions Bot added the ci label Jul 29, 2026
@JeanExtreme002
JeanExtreme002 merged commit 0b82548 into main Jul 29, 2026
9 checks passed
@github-actions
github-actions Bot deleted the fix/live-feed-empty-backend branch July 29, 2026 13:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant