Fix stop() leaving ghost threads that block reconnection#192
Open
Fernandosnulerin wants to merge 1 commit intotwrecked:masterfrom
Open
Fix stop() leaving ghost threads that block reconnection#192Fernandosnulerin wants to merge 1 commit intotwrecked:masterfrom
Fernandosnulerin wants to merge 1 commit intotwrecked:masterfrom
Conversation
Fixes twrecked#71. When stop() was called (without logout=True), it stopped the background worker and media library but left the backend event stream thread running. The abandoned thread would hold stale SSE/MQTT connections and attempt its own re-logins, interfering with any new PyArlo instance created in the same process. This made in-process reconnection impossible — the only recovery was killing the entire process. Three bugs contributed: 1. PyArlo.stop() only called self._be.logout() when logout=True. With the default logout=False, the backend was never stopped at all. 2. The re-login loop in _event_main() — `while not self._logged_in` — did not check _stop_thread. Once the event stream dropped and the thread entered the re-login loop, setting _stop_thread had no effect until a login succeeded (which it never would with a stale session). 3. _event_stop_loop() set the flag but did not notify threads blocked on self._lock.wait(5), causing up to a 5-second delay before the thread noticed the stop request. Changes: - Add ArloBackEnd.stop() that signals the event thread, disconnects the client, and joins the thread with a 10s timeout. - Update PyArlo.stop() to always call self._be.stop() (or .logout() when logout=True). - Check _stop_thread in the re-login loop so stop requests are honoured promptly. - Call self._lock.notify_all() in _event_stop_loop() to wake any thread blocked on the condition variable. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.
Summary
Fixes #71.
When
stop()is called, the backend event stream thread keeps running — it holds stale SSE/MQTT connections and attempts its own re-logins, making it impossible to create a newPyArloinstance in the same process. The only recovery is killing the entire process.Three bugs contributed:
PyArlo.stop()never stops the backend — it only callsself._be.logout()whenlogout=True. With the defaultlogout=False, the event stream thread is never stopped.The re-login loop ignores the stop flag —
_event_main()haswhile not self._logged_inwithout checking_stop_thread. Once the event stream drops, the thread enters an infinite re-login loop that cannot be interrupted bystop()._event_stop_loop()doesn't wake blocked threads — it sets the flag but doesn't callnotify_all(), so threads blocked onself._lock.wait(5)don't see it for up to 5 seconds.Changes
ArloBackEnd.stop()— signals the event thread, disconnects the MQTT/SSE client, and joins the thread with a 10s timeoutPyArlo.stop()to always callself._be.stop()(or.logout()whenlogout=True)_stop_threadin the re-login loop so stop requests are honoured immediatelyself._lock.notify_all()in_event_stop_loop()to wake blocked threadsMotivation
Any long-running application that needs to reconnect to Arlo (e.g. Home Assistant, or any polling service) hits this — after the session goes stale, creating a new
PyArloinstance in the same process fails because the old instance's ghost threads compete for the same Arlo account, triggering Cloudflare 403/500 blocks. The standard workaround has been to restart the entire process, but proper cleanup instop()makes in-process reconnection work reliably.Test plan
arlo.stop()then verifyArloBackgroundWorkerandArloEventStreamthreads are no longer alivearlo.stop(logout=True)then create a newPyArlo()in the same process — should connect cleanlyarlo.stop()while the event thread is in the re-login loop — thread should exit promptly🤖 Generated with Claude Code