fix(vessels): protect Hormuz VesselAPI rows from AISStream eviction
AISStream's live US-coast feed crowds vessel_last_known past _MAX_VESSELS (6000) and the 'evict oldest seen_at' pass reaps the Hormuz VesselAPI rows, which are only refreshed every VESSELAPI_INTERVAL (~4.8h). Result: the poller was fetching 50 Hormuz vessels per 2xx but /api/vessels?bbox=Hormuz stayed empty. Eviction now skips src=vesselapi rows (falls back to evictable-only, clamped) so the blind-spot box stays resident between polls.
This commit is contained in:
parent
fc2c7c28c3
commit
86b412dd37
2 changed files with 45 additions and 1 deletions
|
|
@ -993,8 +993,18 @@ async def upsert_vessel(marker: dict) -> None:
|
||||||
vessel_last_known[vid] = stored
|
vessel_last_known[vid] = stored
|
||||||
if len(vessel_last_known) > _MAX_VESSELS:
|
if len(vessel_last_known) > _MAX_VESSELS:
|
||||||
excess = len(vessel_last_known) - int(_MAX_VESSELS * 0.9)
|
excess = len(vessel_last_known) - int(_MAX_VESSELS * 0.9)
|
||||||
|
# AISStream's live US-coast feed can crowd the shared store past
|
||||||
|
# _MAX_VESSELS and evict the Hormuz VesselAPI rows (refreshed only
|
||||||
|
# every VESSELAPI_INTERVAL) as "oldest". Keep src=vesselapi rows
|
||||||
|
# resident so the blind-spot box stays visible between polls.
|
||||||
|
evictable = [
|
||||||
|
k for k, v in vessel_last_known.items()
|
||||||
|
if (v.get("extra") or {}).get("src") != "vesselapi"
|
||||||
|
]
|
||||||
|
if len(evictable) < excess:
|
||||||
|
excess = len(evictable)
|
||||||
oldest = sorted(
|
oldest = sorted(
|
||||||
vessel_last_known,
|
evictable,
|
||||||
key=lambda k: vessel_last_known[k].get("seen_at", ""),
|
key=lambda k: vessel_last_known[k].get("seen_at", ""),
|
||||||
)[:excess]
|
)[:excess]
|
||||||
for k in oldest:
|
for k in oldest:
|
||||||
|
|
|
||||||
|
|
@ -298,6 +298,40 @@ def _make_get_client(client):
|
||||||
return _get_client
|
return _get_client
|
||||||
|
|
||||||
|
|
||||||
|
def test_eviction_keeps_vesselapi_rows(monkeypatch):
|
||||||
|
"""AISStream crowding past _MAX_VESSELS must not reap Hormuz VesselAPI rows."""
|
||||||
|
from ws_manager import manager
|
||||||
|
from live_layers import _MAX_VESSELS, upsert_vessel
|
||||||
|
|
||||||
|
_patch_side_effects(monkeypatch)
|
||||||
|
monkeypatch.setattr("live_layers._MAX_VESSELS", 10)
|
||||||
|
vessel_last_known.clear()
|
||||||
|
manager._queues.clear()
|
||||||
|
manager._viewports.clear()
|
||||||
|
|
||||||
|
# One Hormuz VesselAPI row with the oldest seen_at — the first thing the
|
||||||
|
# old "evict oldest" logic would reap — plus enough AISStream rows to
|
||||||
|
# exceed the cap.
|
||||||
|
vessel_last_known["422050100"] = {
|
||||||
|
"id": "422050100", "lat": 26.5, "lon": 56.3, "label": "HORMUZ STAR",
|
||||||
|
"extra": {"src": "vesselapi"}, "seen_at": "2026-08-29T00:00:00+00:00",
|
||||||
|
}
|
||||||
|
for i in range(10):
|
||||||
|
vid = f"3{i:08d}"
|
||||||
|
vessel_last_known[vid] = {
|
||||||
|
"id": vid, "lat": 35.0 + i * 0.01, "lon": -79.0, "label": vid,
|
||||||
|
"extra": {"src": "aisstream"},
|
||||||
|
"seen_at": f"2026-08-29T0{i}:00:00+00:00",
|
||||||
|
}
|
||||||
|
|
||||||
|
# One more AISStream marker pushes past the cap and triggers eviction.
|
||||||
|
asyncio.run(upsert_vessel({"id": "399999999", "lat": 36.0, "lon": -78.0, "label": "NEW"}))
|
||||||
|
|
||||||
|
assert "422050100" in vessel_last_known # VesselAPI row survives
|
||||||
|
assert vessel_last_known["422050100"]["extra"]["src"] == "vesselapi"
|
||||||
|
assert len(vessel_last_known) <= 10
|
||||||
|
|
||||||
|
|
||||||
# ── Independent-provider idle behaviour ───────────────────────────────────
|
# ── Independent-provider idle behaviour ───────────────────────────────────
|
||||||
# VesselAPI must never be gated on AISStream (or vice versa): a missing key on
|
# VesselAPI must never be gated on AISStream (or vice versa): a missing key on
|
||||||
# one provider leaves the other running. ``_StopLoop`` is a BaseException so
|
# one provider leaves the other running. ``_StopLoop`` is a BaseException so
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue