Skip to content

Web UI: update response includes the full wuid_data registration table on every poll, growing unboundedly with uptime #300

Description

@jtruland

Describe the bug

The update response carries the entire wuid_data subscriber-registration table on every poll. Since WUID_EXPIRY_TIME = 14400 (4 hours), that table grows with uptime and with how busy the system is, so the per-poll payload grows without any corresponding increase in useful information.

The web UI polls at 1 Hz (setInterval(do_update, 1000)), so a browser on a busy trunked system ends up parsing a large, mostly-unchanged JSON blob once or twice a second. The tab saturates a CPU core and outstanding requests accumulate faster than they drain. On the reporting user's desktop this degraded mouse and keyboard input across the whole OS, not just the tab.

The distinguishing symptom is that it gets worse the longer multi_rx has been running, and a restart temporarily resolves it.

Measurements

Live P25 Phase 1 simulcast, one control channel, 7 adjacent sites. Response composition at 44 minutes of uptime:

total response          164546 bytes
  trunk_update          164090
      wuid_data         159198   (771 entries)   <-- 97% of the payload
      frequency_data      1862   (12 entries)
  channel_update           373
  call_log                  36
  rx_update                 39

Growth over 62 seconds on the same run:

elapsed response bytes wuid entries
t+1s 165,979 779
t+31s 167,153 785
t+62s 168,534 791

That is ~12 new registrations per minute, ~+1.2 KB per 30s. An earlier capture on the same system shortly after startup was 34,932 bytes total, versus 164,546 at 44 minutes.

Extrapolating (and this is an extrapolation, not a measurement): at ~12/min against a 4-hour expiry, this system would reach roughly 2,900 entries at equilibrium — on the order of 600 KB per response, delivered 1–2 times per second.

Client-side cost

update_sub_reg() iterates the whole table on every poll:

// main.js:991
if (!s || typeof s !== 'object' || !s.wuid_data || typeof s.wuid_data !== 'object') return;
// main.js:995
Object.values(s.wuid_data).forEach(function (obj) { ... });
// main.js:1188
update_sub_reg(d[nac]['wuid_data'], d[nac]['sysid']);

Notably, legacy-main.js does not reference wuid_data at all — so the legacy UI still receives the payload but never parses or renders it, which matches the observed difference in CPU cost between the two UIs.

Measured browser behaviour with the new UI: browser-process CPU stepped to ~95% of one core about 2.5 minutes after tab load and held there for 14 consecutive 30-second intervals, with all memory pools flat (browser RSS 412→427 MB over 9 min, NetworkService flat, renderer noisy with no trend). Sustained CPU with no memory growth is consistent with repeatedly parsing and re-rendering a large payload that is then discarded, rather than with anything accumulating.

Suggested fix

Any of these would address it; the first is probably the smallest change:

  1. Do not include wuid_data in the routine update response. Serve it via its own command, fetched only when the subscriber-registration panel is actually open.
  2. Send deltas — new/changed/expired registrations since the client's last update — rather than the full table each time.
  3. Cap or paginate what is sent, since a UI panel cannot usefully display thousands of rows anyway.

There is currently no configuration option to disable registration tracking, so a user on a busy system has no workaround short of using the legacy UI or restarting multi_rx periodically.

System Information

  • OP25: master 28f2c40
  • Server: Raspbian 12 (bookworm), Raspberry Pi 4 Model B Rev 1.1, GNU Radio 3.10.5.1, Python 3.11.2
  • Browser: Chrome on Windows 11, separate machine, reaching the host over a VPN

Relationship to #299

Separate defect, same victim. #299 is an unbounded get_full_config request storm caused by a missing site_alias; fixing that removed the extra request volume and the ERR_INSUFFICIENT_RESOURCES failures, and NetworkService memory went flat — but the sustained CPU remained, which is what led here. Both are reachable from a stock configuration.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions