Skip to content

fix(mqtt): run the TLS handshake off the accept loop with a timeout - #244

Open
NicolasBonduel wants to merge 2 commits into
Python-roborock:mainfrom
NicolasBonduel:fix/mqtt-tls-handshake-timeout
Open

NicolasBonduel wants to merge 2 commits into
Python-roborock:mainfrom
NicolasBonduel:fix/mqtt-tls-handshake-timeout

Conversation

@NicolasBonduel

Copy link
Copy Markdown

Fixes #238.

Problem

The MQTT TLS proxy does the TLS handshake inside its accept loop, on a blocking socket with no timeout. If one client stalls before finishing the handshake, accept() stays blocked for everyone else, including vacuums, Home Assistant and apps, until the OS gives up on that socket. On a lossy Wi-Fi link that took about 15 minutes per stalled attempt. The robot's retries kept stalling the same way, and the listener stayed blocked for hours until the add-on was restarted. Connections that were already established kept working; only new ones were blocked.

Fix

The accept loop now only calls accept() and hands the raw socket to a per-client thread. That thread sets a 10 s timeout, does the handshake, clears the timeout and then runs the existing _handle_client. A stalled handshake now ties up only its own thread and fails with the existing TLS handshake failed ... warning after 10 s. Plain MQTT mode (tls_enabled=False) goes through the same path.

Clearing the timeout before the relay keeps established connections in blocking mode, as before. The wait for the MQTT CONNECT packet is still unbounded, but it now happens on the client's own thread, so it can't block other clients.

Tests

Two new end-to-end tests in tests/test_mqtt_tls_proxy.py start the proxy on a free local port with a self-signed certificate:

  • a TLS handshake succeeds while another client holds an idle TCP connection open
  • the idle connection is closed by the server once the handshake timeout passes

Both tests fail on main and pass with the fix. Full suite: 460 passed. These tests are the first to build a real TLS context, so they show the existing ssl.TLSVersion.TLSv1 is deprecated warning from the intentional TLS 1.0 support for older firmware.

The reproduction script in #238 shows the same behaviour against a running stack.

🤖 Generated with Claude Code

The MQTT TLS proxy did the TLS handshake inside its accept loop on a
blocking socket with no timeout. One client that stalled mid-handshake
blocked accept() for every other client until the OS gave up on the
socket, which on a lossy link took about 15 minutes per attempt.

Accept, then hand the raw socket to a per-client thread that does a
handshake bounded by a 10 s timeout. The timeout is cleared before the
relay starts, so established connections behave as before.

Fixes Python-roborock#238

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 10, 2026 18:51
@NicolasBonduel
NicolasBonduel marked this pull request as ready for review November 10, 2026 18:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The regression test can pass despite a blocked accept loop because the idle client’s 0.5-second server timeout expires within the healthy client’s 2-second deadline, masking serialized handshakes.

1 open issue
What changed in this PR

This PR addresses #238 by preventing a stalled TLS handshake from blocking new connections to the MQTT proxy.

Changes:

  • Moves connection setup into per-client threads.
  • Adds a 10-second handshake timeout and restores blocking mode before client handling.
  • Adds real-TLS tests for concurrent connection acceptance and stalled-handshake cleanup.
File Description
tests/​test_mqtt_tls_proxy.py Adds a TLS fixture and two stalled-handshake regression tests.
src/​roborock_local_server/​bundled_backend/​mqtt_tls_proxy_server/​server.py Moves handshakes off the accept loop and bounds their duration.

Review effort: Balanced

Comment thread tests/test_mqtt_tls_proxy.py Resolved
The idle client's 0.5 s server timeout expired within the healthy client's 2 s deadline,
so the test also passed when handshakes still ran on the accept loop.
That test now uses a 10 s handshake timeout; the timeout test keeps 0.5 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MQTT TLS proxy: one stalled TLS handshake blocks all new connections (no handshake timeout)

2 participants