MQTT client doesn't recover after printer disconnect — stuck retrying dead socket #105

Closed
opened 2026-07-31 15:00:07 +02:00 by p2l · 1 comment
Contributor

When the printer drops off the network (e.g. powered off) and comes back later at the same IP, kx-bridge does not reconnect on its own. It gets stuck in a tight loop:

[..] ERROR kobrax.mqtt: send error: 'NoneType' object has no attribute 'sendall', reconnecting…
[..] WARNING kobrax.mqtt: Connection lost - reconnecting...

repeating every ~3 seconds indefinitely, even with the printer back online and reachable (confirmed via ping and nc -zv <ip> 9883 succeeding).

Root cause appears to be that the reconnect handler doesn't null out / rebuild the socket object before retrying — it keeps calling .sendall() on a socket reference that's already None, so the "reconnect" attempt never actually opens a new connection. A full container restart (docker restart) resolves it immediately, with a clean TLS handshake and MQTT CONNACK, confirming this is a bridge-side reconnect bug rather than a network/printer issue.

Expected: after a connection loss, the MQTT client should tear down and re-establish a fresh socket/TLS session rather than looping on a stale reference.

Environment:

  • kx-bridge image: gitea.it-drui.de/viewit/kx-bridge:latest, digest sha256:cd57bb43364944ba214d45c4c3b3f14ec9828fb0d78300c3e4d8453414d7f3a6
  • Printer: Anycubic Kobra X, firmware 1.2.0.6
  • Deployment: Docker Compose on Debian/Kontaina LXC
When the printer drops off the network (e.g. powered off) and comes back later at the same IP, kx-bridge does not reconnect on its own. It gets stuck in a tight loop: ``` [..] ERROR kobrax.mqtt: send error: 'NoneType' object has no attribute 'sendall', reconnecting… [..] WARNING kobrax.mqtt: Connection lost - reconnecting... ``` repeating every ~3 seconds indefinitely, even with the printer back online and reachable (confirmed via `ping` and `nc -zv <ip> 9883` succeeding). Root cause appears to be that the reconnect handler doesn't null out / rebuild the socket object before retrying — it keeps calling `.sendall()` on a socket reference that's already `None`, so the "reconnect" attempt never actually opens a new connection. A full container restart (`docker restart`) resolves it immediately, with a clean TLS handshake and MQTT CONNACK, confirming this is a bridge-side reconnect bug rather than a network/printer issue. **Expected:** after a connection loss, the MQTT client should tear down and re-establish a fresh socket/TLS session rather than looping on a stale reference. **Environment:** - kx-bridge image: `gitea.it-drui.de/viewit/kx-bridge:latest`, digest `sha256:cd57bb43364944ba214d45c4c3b3f14ec9828fb0d78300c3e4d8453414d7f3a6` - Printer: Anycubic Kobra X, firmware 1.2.0.6 - Deployment: Docker Compose on Debian/Kontaina LXC
Owner

Thanks for the excellent report, @p2l — precise repro conditions and the exact log pattern made this straightforward to track down.

Root cause (slightly different from what you suspected, same symptom): there are actually two independent reconnect paths that could race each other — the MQTT reader thread's own reconnect (triggered by a failed keepalive) and a second reconnect triggered from publish()/publish_web() when a send fails. Neither had any protection against running concurrently, so two threads could end up opening competing TLS handshakes to the printer at once, which likely only accepts one mTLS session at a time — the two attempts interfered with each other and neither ever converged.

On top of that, the bridge's own status-poll loop made the problem invisible: it only checked whether the printer was TCP-reachable (a raw connect test on port 9883) to decide if it should attempt reconnecting, not whether the actual MQTT/TLS session was still alive. A NoneType has no attribute 'sendall' failure inside publish() gets swallowed internally and returns None instead of raising — so the poll loop had no way to notice the session was dead and just kept assuming everything was fine.

Fix:

  1. Reconnect attempts are now serialized with a lock — if one thread's reconnect is already in flight, a second caller waits for it to finish instead of starting a competing handshake.
  2. The poll loop now checks the MQTT client's actual connection state (not just TCP reachability) and switches to its existing offline-recovery branch as soon as it detects the session is gone — which already handles waiting for the printer to become reachable again and reconnecting cleanly.

Fix is committed on nightly with test coverage for both the concurrent-reconnect race and the poll-loop detection gap; will go out with the next nightly build. If you hit this again afterward, a fresh log capture of the same disconnect/reconnect cycle would help confirm it's fully resolved.

Thanks for the excellent report, @p2l — precise repro conditions and the exact log pattern made this straightforward to track down. **Root cause (slightly different from what you suspected, same symptom):** there are actually two independent reconnect paths that could race each other — the MQTT reader thread's own reconnect (triggered by a failed keepalive) and a second reconnect triggered from `publish()`/`publish_web()` when a send fails. Neither had any protection against running concurrently, so two threads could end up opening competing TLS handshakes to the printer at once, which likely only accepts one mTLS session at a time — the two attempts interfered with each other and neither ever converged. On top of that, the bridge's own status-poll loop made the problem invisible: it only checked whether the printer was *TCP-reachable* (a raw connect test on port 9883) to decide if it should attempt reconnecting, not whether the actual MQTT/TLS session was still alive. A `NoneType has no attribute 'sendall'` failure inside `publish()` gets swallowed internally and returns `None` instead of raising — so the poll loop had no way to notice the session was dead and just kept assuming everything was fine. **Fix:** 1. Reconnect attempts are now serialized with a lock — if one thread's reconnect is already in flight, a second caller waits for it to finish instead of starting a competing handshake. 2. The poll loop now checks the MQTT client's actual connection state (not just TCP reachability) and switches to its existing offline-recovery branch as soon as it detects the session is gone — which already handles waiting for the printer to become reachable again and reconnecting cleanly. Fix is committed on `nightly` with test coverage for both the concurrent-reconnect race and the poll-loop detection gap; will go out with the next nightly build. If you hit this again afterward, a fresh log capture of the same disconnect/reconnect cycle would help confirm it's fully resolved.
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: viewit/KX-Bridge-Release#105
No description provided.