XMPP: syncNow 1-seconde polling zonder short-circuit — constante CPU op een idle sessie #1423

Closed
opened 2026-08-09 18:29:19 +00:00 by brenno · 0 comments
Owner

Bevinding

lib/xmpp/xmpp_collab_launch.dartstart() (regel 208) start een Timer.periodic met een default interval van 1 seconde die syncNow() aanroept. syncNow() (regels 167–205) draait elke ronde altijd alle retry-functies, zonder short-circuit als er niets veranderd is:

  • presence.retryPending() — itereert alle _pending-entries (regel 171)
  • chat.retryPending() — itereert de hele _pending-lijst (regel 172)
  • Host: keyExchange.ensureKeyed() — itereert alle directory.knownDevices (regel 174)
  • Guest: snapshotChannel.retryPending() — itereert alle _assembled-entries (regel 187)

Het design-document noemt "Push, not poll" (regel 13–17) als uitgangspunt — XMPP is push, stanzas arriveren op de demux. Maar de syncNow-loop is een poll die elke seconde alle buffers opnieuw probeert, ook als er geen nieuwe stanzas zijn aangekomen.

Waarom de poll bestaat (en waarom het een performance-probleem is)

De poll is nodig omdat stanzas kunnen aankomen vóórdat de key-state compleet is (een joiner ziet presence/chat vóór zijn keyshare, §6). De retryPending-functies heropenen gebufferde verzegelingen zodra de sleutel beschikbaar is. Maar als er niets in de buffers staat (_pending is leeg), is elke ronde pure overhead — een iteratie over een lege collectie en een await per retry-functie.

Op een idle sessie (geen activiteit, geen newcomers) draait dit elke seconde 4 async-functies die niets doen. Dat is niet catastrofaal, maar het is onnodige CPU en wakeups — op een laptop betekent het dat de app elke seconde uit idle wordt gehaald.

Trust boundary

Interne logica — geen externe aanvaller. Performance-degradatie op de lange termijn.

Impact

  • Constante CPU-wakeups elke seconde, ook op een idle sessie.
  • Op batterij-gevoelige devices (laptops op batterij, toekomstige mobiele targets) verhoogt dit het stroomverbruik onnodig.
  • De overhead is klein per ronde, maar het is 60 rondes per minuut, 3600 per uur, constant.

Oplossingsrichting

  1. Short-circuit als alle buffers leeg zijn: voeg een hasPending-check toe aan elke brick (presence, chat, snapshot, keyExchange). Als geen van alle buffers pending-entries heeft, skip de ronde. Dit is een één-regel guard aan het begin van syncNow.
  2. Event-gedreven in plaats van poll: in plaats van een timer, laat de demux een signaal geven als een nieuwe stanza arriveert die een retry nodig maakt (bijv. een keyshare die de epoch-sleutel installeert → trigger retryPending op alle bricks). De poll kan dan vervallen of worden teruggebracht naar een langzame fallback (bijv. elke 30 seconden) voor het geval een signaal wordt gemist.
  3. Aanvullend: verhoog het default-interval naar 2–5 seconden als de event-gedreven route (optie 2) niet direct haalbaar is. De afruil is dat een newcomer maximaal 2–5 seconden wacht op keying in plaats van 1.

Locatie

  • lib/xmpp/xmpp_collab_launch.dart regels 167–205 (syncNow), 207–211 (start)
  • lib/xmpp/xmpp_presence_beacon.dart regels 150–154 (retryPending)
  • lib/xmpp/xmpp_chat.dart regels 188–202 (retryPending)
  • lib/xmpp/xmpp_snapshot.dart regels 121–125 (retryPending)
  • lib/xmpp/xmpp_key_exchange.dart regels 154–165 (ensureKeyed)

Severity

LOW — onnodige CPU/wakeups, geen crash of data-loss, maar relevant voor batterij-gevoelige targets.

## Bevinding `lib/xmpp/xmpp_collab_launch.dart` — `start()` (regel 208) start een `Timer.periodic` met een default interval van 1 seconde die `syncNow()` aanroept. `syncNow()` (regels 167–205) draait elke ronde **altijd** alle retry-functies, zonder short-circuit als er niets veranderd is: - `presence.retryPending()` — itereert alle `_pending`-entries (regel 171) - `chat.retryPending()` — itereert de hele `_pending`-lijst (regel 172) - Host: `keyExchange.ensureKeyed()` — itereert alle `directory.knownDevices` (regel 174) - Guest: `snapshotChannel.retryPending()` — itereert alle `_assembled`-entries (regel 187) Het design-document noemt "Push, not poll" (regel 13–17) als uitgangspunt — XMPP is push, stanzas arriveren op de demux. Maar de `syncNow`-loop is een poll die elke seconde alle buffers opnieuw probeert, ook als er geen nieuwe stanzas zijn aangekomen. ### Waarom de poll bestaat (en waarom het een performance-probleem is) De poll is nodig omdat stanzas kunnen aankomen vóórdat de key-state compleet is (een joiner ziet presence/chat vóór zijn keyshare, §6). De `retryPending`-functies heropenen gebufferde verzegelingen zodra de sleutel beschikbaar is. Maar als er niets in de buffers staat (`_pending` is leeg), is elke ronde pure overhead — een iteratie over een lege collectie en een await per retry-functie. Op een idle sessie (geen activiteit, geen newcomers) draait dit elke seconde 4 async-functies die niets doen. Dat is niet catastrofaal, maar het is onnodige CPU en wakeups — op een laptop betekent het dat de app elke seconde uit idle wordt gehaald. ### Trust boundary Interne logica — geen externe aanvaller. Performance-degradatie op de lange termijn. ### Impact - Constante CPU-wakeups elke seconde, ook op een idle sessie. - Op batterij-gevoelige devices (laptops op batterij, toekomstige mobiele targets) verhoogt dit het stroomverbruik onnodig. - De overhead is klein per ronde, maar het is 60 rondes per minuut, 3600 per uur, constant. ### Oplossingsrichting 1. **Short-circuit als alle buffers leeg zijn:** voeg een `hasPending`-check toe aan elke brick (presence, chat, snapshot, keyExchange). Als geen van alle buffers pending-entries heeft, skip de ronde. Dit is een één-regel guard aan het begin van `syncNow`. 2. **Event-gedreven in plaats van poll:** in plaats van een timer, laat de demux een signaal geven als een nieuwe stanza arriveert die een retry nodig maakt (bijv. een keyshare die de epoch-sleutel installeert → trigger `retryPending` op alle bricks). De poll kan dan vervallen of worden teruggebracht naar een langzame fallback (bijv. elke 30 seconden) voor het geval een signaal wordt gemist. 3. **Aanvullend:** verhoog het default-interval naar 2–5 seconden als de event-gedreven route (optie 2) niet direct haalbaar is. De afruil is dat een newcomer maximaal 2–5 seconden wacht op keying in plaats van 1. ### Locatie - `lib/xmpp/xmpp_collab_launch.dart` regels 167–205 (`syncNow`), 207–211 (`start`) - `lib/xmpp/xmpp_presence_beacon.dart` regels 150–154 (`retryPending`) - `lib/xmpp/xmpp_chat.dart` regels 188–202 (`retryPending`) - `lib/xmpp/xmpp_snapshot.dart` regels 121–125 (`retryPending`) - `lib/xmpp/xmpp_key_exchange.dart` regels 154–165 (`ensureKeyed`) ### Severity LOW — onnodige CPU/wakeups, geen crash of data-loss, maar relevant voor batterij-gevoelige targets.
brenno 2026-08-09 20:56:33 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
LibreKAT/Ocideck#1423
No description provided.