Panning the mobile map more than twice a second leaves the stream locked to the old viewport #55

Open
opened 2026-11-03 18:52:29 +00:00 by skippy-bot · 0 comments

Problem

update_bounds is capped at 120 frames per minute and keyed on the peer IP, not the socket. The documented SwiftUI client pushes a region change on every pan, so a normal drag exhausts the budget; the denial reply is never read by the sample client, and the server keeps filtering on the rectangle it held when the budget ran out. The map shows the new area while packets keep arriving for the old one. Clients behind one CGNAT egress share the budget, and the docs say the limit is per socket and do not list update_bounds at all.

Evidence

At 2f150f66:

  • lib/aprsme_web/channels/mobile_channel.ex:78 sets @update_bounds_limit 120.
  • lib/aprsme_web/channels/mobile_channel.ex:138 returns the deny reply from the with and never reaches apply_bounds_update, so the stored bounds stay stale.
  • lib/aprsme_web/channels/mobile_channel.ex:436 keys the bucket mobile_channel:#{op}:#{ip}.
  • lib/aprsme_web/channels/mobile_channel.ex:454 replies Rate limit exceeded; retry later with retry_after_ms.
  • docs/mobile-api.md:227 says expensive operations are limited to 30 per socket per minute and does not mention update_bounds; :464 shows the sample client pushing bounds with no reply handler and no debounce.

Expected

The budget is documented and keyed per socket as the docs claim, the documented client debounces, and a denied pan does not leave the server filtering on a stale rectangle without telling the client to resubscribe.

How to verify

Extend the existing sustained panning test at test/aprsme_web/channels/mobile_channel_test.exs:320 to assert the socket's bounds are unchanged after the 121st push and that a packet inside the new bounds is not delivered.

Acceptance criteria

  • The documented limit and the enforced limit are the same number and keyed the same way.
  • The docs list update_bounds with its budget.
  • A denied update either applies the bounds without registering or replies with an instruction the client can act on.

Effort: S
Lens: api

## Problem `update_bounds` is capped at 120 frames per minute and keyed on the peer IP, not the socket. The documented SwiftUI client pushes a region change on every pan, so a normal drag exhausts the budget; the denial reply is never read by the sample client, and the server keeps filtering on the rectangle it held when the budget ran out. The map shows the new area while packets keep arriving for the old one. Clients behind one CGNAT egress share the budget, and the docs say the limit is per socket and do not list `update_bounds` at all. ## Evidence At `2f150f66`: - `lib/aprsme_web/channels/mobile_channel.ex:78` sets `@update_bounds_limit 120`. - `lib/aprsme_web/channels/mobile_channel.ex:138` returns the deny reply from the `with` and never reaches `apply_bounds_update`, so the stored bounds stay stale. - `lib/aprsme_web/channels/mobile_channel.ex:436` keys the bucket `mobile_channel:#{op}:#{ip}`. - `lib/aprsme_web/channels/mobile_channel.ex:454` replies `Rate limit exceeded; retry later` with `retry_after_ms`. - `docs/mobile-api.md:227` says expensive operations are limited to 30 per socket per minute and does not mention `update_bounds`; `:464` shows the sample client pushing bounds with no reply handler and no debounce. ## Expected The budget is documented and keyed per socket as the docs claim, the documented client debounces, and a denied pan does not leave the server filtering on a stale rectangle without telling the client to resubscribe. ## How to verify Extend the existing sustained panning test at `test/aprsme_web/channels/mobile_channel_test.exs:320` to assert the socket's bounds are unchanged after the 121st push and that a packet inside the new bounds is not delivered. ## Acceptance criteria - [ ] The documented limit and the enforced limit are the same number and keyed the same way. - [ ] The docs list `update_bounds` with its budget. - [ ] A denied update either applies the bounds without registering or replies with an instruction the client can act on. **Effort:** S **Lens:** api
Sign in to join this conversation.
No description provided.