LogicMachine Forum
Visu localbus.js reconnect behaviour, reuse object registry, reconnect without delay - Printable Version

+- LogicMachine Forum (https://forum.logicmachine.net)
+-- Forum: LogicMachine eco-system (https://forum.logicmachine.net/forumdisplay.php?fid=1)
+--- Forum: Visu (https://forum.logicmachine.net/forumdisplay.php?fid=24)
+--- Thread: Visu localbus.js reconnect behaviour, reuse object registry, reconnect without delay (/showthread.php?tid=6543)



Visu localbus.js reconnect behaviour, reuse object registry, reconnect without delay - savaskorkmaz - 05.09.2026

Setup: LM firmware 2025, Visu, apps/js/localbus.js (v=20251031), Android kiosk panels (WebView) plus desktop Chrome for comparison.

PROBLEM

On some corporate networks a middlebox (IPS / application control / proxy / NAC policy) resets established TCP sessions a few seconds after they open. Visu's live-data WebSocket is hit by this; the classic HTTP-polling visualization is not. With the current client logic the visible effect and the server load are much larger than necessary:

- on every WebSocket close, localbus.js waits 1 s,
- then re-downloads the FULL object registry via POST /apps/localbus.lp (about 285 KB in our project, 2832 objects),
- and only then reopens the WebSocket.

So each reset costs the user ~3 s of "reconnecting" spinner with frozen values, and costs the LM a full registry regeneration.

Field symptom that started this: WebSocket killed 1-1.5 s after open, floor plan stays rendered, spinner flashes every ~3 s, values freeze. A laptop on the same LAN (not subject to the policy) worked fine.

WHAT WE MEASURED

Lab: a transparent TCP relay between client and LM that kills every session after N seconds.

1) Current client, Android panel, kill after 5 s
  WS lifetime 5.0 s, visible gap 2.9-3.0 s per cycle, ~37 % of the time disconnected,
  server work per cycle: registry POST 285 KB + WS handshake.

2) Current client, Android panel, kill after 2 s
  The registry POST never completes, page stays blank with a permanent spinner.

3) Patched client (see below), desktop Chrome, kill after 5 s
  WS lifetime 5.06 s, visible gap 22-37 ms, < 1 % disconnected, server work: WS handshake only (~4 KB).

4) Patched client, desktop Chrome, kill after 1.5 s (the field case)
  WS lifetime 1.55 s, gap avg 25 ms / max 29 ms, 1.5 % disconnected, live values keep flowing.

5) Control, kill only after 15 s idle
  No disconnects at all: the client "ping" every 10 s keeps the socket alive, so idle timeouts >= 11 s are not the cause.

Also verified: the server ACCEPTS a new WebSocket in the same session without a preceding registry POST (opened in 21 ms, live messages received).

OBSERVED CLIENT LOGIC (localbus.js)

onclose -> setstate(false) -> onerror() -> setTimeout(init, 1000) -> init(): POST /apps/localbus.lp (full registry) -> register() -> start(): new WebSocket.
No backoff. The registry is re-fetched on every reconnect although objectstore already holds it.

REQUESTED CHANGE (small)

1. On WebSocket close, if objectstore is already populated, reopen the WebSocket directly (start()), without the 1 s delay and without re-downloading the registry.
2. Keep the full init() path only when the registry is empty or the previous session was rejected (lived < ~300 ms), so an expired session still recovers.
3. Add a simple cap/backoff (e.g. max 60 fast reconnects per minute, then 1 s delay) to protect the server under persistent resets.
4. Optional: expose reconnect counters (count, last lifetime, last gap) on the localbus object for diagnostics.

WHY IT MATTERS FOR THE SERVER TOO

Under a 1.5 s reset policy with 20 panels the current logic makes the LM regenerate about 1.4 MB/s of registry JSON continuously; the patched logic reduces that to WebSocket handshakes (about 60 KB/s). On healthy networks the change has zero effect.

REFERENCE IMPLEMENTATION (tested as Visu Custom JS, wraps start/onerror only)

(function () {
  var CFG = { FAST_LIMIT_PER_MIN: 60, SLOW_WAIT_MS: 1000, REJECT_LIFE_MS: 300, WAIT_FOR_LOCALBUS_MS: 30000 };
  if (window.__sonaLb) return;
  window.__sonaLb = { version: 'V1', active: false, fast: 0, slow: 0, full: 0, lastLifeMs: null, lastGapMs: null, sessions: 0 };
  function install() {
    var lb = window.localbus;
    if (!lb || typeof lb.start !== 'function' || typeof lb.onerror !== 'function' || typeof lb.init !== 'function') {
      console.warn('[sona-lb] unexpected localbus structure, patch not applied'); return;
    }
    var S = window.__sonaLb, hist = [], startT = 0, closeT = 0;
    var origStart = lb.start, origOnError = lb.onerror;
    lb.start = function () {
      startT = Date.now(); S.sessions++;
      if (closeT) { S.lastGapMs = startT - closeT; closeT = 0; }
      return origStart.apply(this, arguments);
    };
    lb.onerror = function () {
      var now = Date.now();
      clearTimeout(this.inittimer); this.inittimer = null;
      closeT = now;
      S.lastLifeMs = startT ? now - startT : null;
      var haveRegistry = this.objectstore && Object.keys(this.objectstore).length > 0;
      var rejected = S.lastLifeMs !== null && S.lastLifeMs < CFG.REJECT_LIFE_MS;
      if (!haveRegistry || rejected) { S.full++; return origOnError.call(this); }  // default path: 1 s + POST + WS
      hist = hist.filter(function (t) { return now - t < 60000; });
      var self = this;
      if (hist.length >= CFG.FAST_LIMIT_PER_MIN) {                                  // cap: wait, but no POST
        S.slow++;
        this.inittimer = setTimeout(function () { self.inittimer = null; self.start(); }, CFG.SLOW_WAIT_MS);
        return;
      }
      hist.push(now); S.fast++;
      this.start();                                                                  // fast path: new WebSocket now
    };
    S.active = true;
    console.info('[sona-lb] fast reconnect active (' + S.version + ')');
  }
  if (window.localbus) { install(); return; }
  var t0 = Date.now();
  var timer = setInterval(function () {
    if (window.localbus) { clearInterval(timer); install(); }
    else if (Date.now() - t0 > CFG.WAIT_FOR_LOCALBUS_MS) { clearInterval(timer); console.warn('[sona-lb] localbus not found'); }
  }, 200);
})();

We can share the raw measurement logs and the relay tool used to reproduce this.