Skip to content

fix(daemon): move blocking MAP poll I/O off the GLib main loop#171

Merged
quad341 merged 1 commit into
mainfrom
fix/obex-worker-offload-97mlk3
Jul 4, 2026
Merged

fix(daemon): move blocking MAP poll I/O off the GLib main loop#171
quad341 merged 1 commit into
mainfrom
fix/obex-worker-offload-97mlk3

Conversation

@quad341

@quad341 quad341 commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Part of the reliability baseline (#167), bead tincan-97mlk.3.

Problem: a slow or hung OBEX transfer froze the whole daemon — ANCS delivery, call control, every D-Bus method — for up to 15s per transfer, because the MAP poll ran folder listings, per-message body transfers, and time.sleep status polling directly on the GLib main loop. Head-of-line blocking that plausibly presents as "ANCS regressed again."

Fix: a serialized OBEX worker thread (tincand/obex_worker.py). MAP folder state requires serialized operations anyway, so one worker owning all blocking OBEX I/O is the natural shape: _poll_tick submits the I/O half and returns immediately; results marshal back to the main loop (where the SQLite store and D-Bus signal emission stay); a session-generation counter drops stale completions across reconnects; overlapping ticks are skipped while a poll is in flight.

Deliberately out of scope: sends stay synchronous — their reply semantics change with the daemon-owned send lifecycle (tincan-xbtct), so converting them now would churn the SendMessage contract twice.

Testing: full suite green (all daemon + GUI tests), plus a new tests/tincand/test_obex_worker.py covering worker ordering/marshalling/busy semantics and the tick/skip/stale-completion/dead-session paths. Live validation to watch for after merge: messages still arrive on poll, and the UI/calls stay responsive while a large MMS body is being fetched.

🤖 Generated with Claude Code

…n-97mlk.3)

A stalled OBEX transfer used to freeze the entire daemon — ANCS
notification delivery, oFono call control, and all D-Bus dispatch — for
up to 15s per transfer, because poll_inbox ran its folder listing,
per-message body transfers, and time.sleep status polling directly on
the main loop. That head-of-line blocking plausibly reads as 'ANCS
regressed again' when the real culprit is a slow message fetch.

- new tincand/obex_worker.py: SerialWorker, a single lazily-started
  worker thread that runs blocking OBEX jobs in submit order (MAP
  folder state requires serialization anyway) and marshals completions
  back to the main loop via GLib.idle_add; InlineWorker test double
- MapBackend: poll_inbox split into _poll_fetch (blocking OBEX I/O,
  worker thread) and _process_poll_results (SQLite store + service
  signals, main loop); poll_inbox keeps its synchronous contract for
  direct callers and tests
- _poll_tick now submits to the worker and returns immediately; ticks
  that fire while a poll is in flight are skipped; a session generation
  counter drops stale completions across disconnect/reconnect; dead-
  session recovery moved to the completion callback
- dbus.mainloop.glib.threads_init() at daemon startup so the worker
  thread may use D-Bus connections

Sends intentionally stay synchronous: their D-Bus reply semantics
change with the daemon-owned send lifecycle (pending→sent→failed)
planned under tincan-xbtct, and converting them now would churn the
SendMessage contract twice.

Co-Authored-By: Claude Fable 5 <[email protected]>
@quad341
quad341 merged commit 938fc52 into main Jul 4, 2026
1 check passed
@quad341
quad341 deleted the fix/obex-worker-offload-97mlk3 branch July 4, 2026 18:10
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.

1 participant