Repository navigation
fix(mqtt): run the TLS handshake off the accept loop with a timeout - #244
Open
NicolasBonduel wants to merge 2 commits into
Open
NicolasBonduel wants to merge 2 commits into
NicolasBonduel wants to merge 2 commits into
Conversation
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>
Contributor
There was a problem hiding this comment.
🟡 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
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

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 existingTLS 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.pystart the proxy on a free local port with a self-signed certificate:Both tests fail on
mainand pass with the fix. Full suite: 460 passed. These tests are the first to build a real TLS context, so they show the existingssl.TLSVersion.TLSv1 is deprecatedwarning 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