fix(garmin): give startup login its own result queue, not the shared one

_startup_login's background thread and _handle_authenticate's shared the
module-level _login_result_queue with no correlation. Since _startup_login
can now time out at 10s while its thread keeps running (from the previous
fix in this wave), a slow-cold-starting subprocess's first explicit
"Connect to Garmin" call could accidentally dequeue the startup thread's
stale result instead of its own fresh one, orphaning the loser's result to
corrupt a later authenticate/complete_mfa call.

_handle_authenticate and _handle_complete_mfa still correctly share
_login_result_queue -- they're two halves of one explicit, MFA-capable
login flow. _startup_login is a background tokenstore resume with no MFA
involved, so it now uses its own private, function-local queue.Queue()
instead, making cross-contamination structurally impossible.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 21:52:48 +02:00
parent e422f86925
commit 487df1f9b9
2 changed files with 66 additions and 5 deletions

View File

@@ -42,7 +42,17 @@ def _startup_login():
must never block indefinitely here -- if it times out, the thread
keeps running and will update _auth_state whenever it eventually
finishes (success or failure), same as today, just without wedging
the whole subprocess unresponsive in the meantime."""
the whole subprocess unresponsive in the meantime.
Deliberately uses its own private, function-local result queue rather
than the module-level _login_result_queue that _handle_authenticate and
_handle_complete_mfa share -- those two are legitimately two halves of
one explicit, MFA-capable login flow and must share a queue for the MFA
handoff to work, but this is a plain background tokenstore resume with
no MFA involved. Sharing the queue here would let this thread's stale
result (arriving after the 10s timeout below) be dequeued by an
unrelated, later authenticate/complete_mfa call instead of that call's
own fresh result."""
global _client, _auth_state
email = os.environ.get("GARMIN_EMAIL")
password = os.environ.get("GARMIN_PASSWORD")
@@ -50,18 +60,19 @@ def _startup_login():
return
_client = Garmin(email, password)
result_queue = queue.Queue() # private to this call, never shared with authenticate/complete_mfa
def _do_login():
try:
_client.login(tokenstore=TOKENSTORE)
_login_result_queue.put(("success", None))
result_queue.put(("success", None))
except Exception as exc:
_login_result_queue.put(("error", str(exc)))
result_queue.put(("error", str(exc)))
threading.Thread(target=_do_login, daemon=True).start()
try:
status, err = _login_result_queue.get(timeout=10)
status, err = result_queue.get(timeout=10)
if status == "success":
_auth_state = "authenticated"
else: