DNS watchdog never corrects a stranded fallback: shared Qt-mainloop bus unusable from the watchdog thread #117

Closed
opened 2026-08-22 12:59:51 -04:00 by mysticalsoap · 0 comments
Owner

Symptom. With a main tunnel up, the journal logs QObject::startTimer: Timers cannot be started from another thread every 15 seconds, and a DNS selection stranded on the fallback is never returned to the primary. The #95 watchdog has never worked in the running service — the thread runs, but every iteration comes back empty.

Cause. The service sets DBusQtMainLoop(set_as_default=True) (aqomui_service.py). dns_manager.current_dns_server() then calls dbus.SystemBus() from the watchdog thread and gets the process-shared connection glued to the Qt main loop, which the main thread owns. libdbus tries to schedule the pending call's timeout through that glue, Qt refuses the cross-thread timer, the property read fails, and current_dns_server returns None — so watch_dns_primary hits its continue on every tick, forever. One Qt warning per tick is the only trace.

Why the tests are green. TestWatchDnsPrimary injects _current; the real D-Bus read has no coverage, and the failure only exists inside the service process where the Qt mainloop is the dbus default.

Fix. Blocking calls need no mainloop: use a private, mainloop-less connection for the read (dbus.SystemBus(private=True, mainloop=dbus.mainloop.NULL_MAIN_LOOP)), closed after each poll so the 15s cadence doesn't leak fds. This also stops sharing one connection between two threads, which dbus-python never promised to survive.

**Symptom.** With a main tunnel up, the journal logs `QObject::startTimer: Timers cannot be started from another thread` every 15 seconds, and a DNS selection stranded on the fallback is never returned to the primary. The #95 watchdog has never worked in the running service — the thread runs, but every iteration comes back empty. **Cause.** The service sets `DBusQtMainLoop(set_as_default=True)` (aqomui_service.py). `dns_manager.current_dns_server()` then calls `dbus.SystemBus()` from the watchdog thread and gets the process-shared connection glued to the Qt main loop, which the main thread owns. libdbus tries to schedule the pending call's timeout through that glue, Qt refuses the cross-thread timer, the property read fails, and `current_dns_server` returns `None` — so `watch_dns_primary` hits its `continue` on every tick, forever. One Qt warning per tick is the only trace. **Why the tests are green.** `TestWatchDnsPrimary` injects `_current`; the real D-Bus read has no coverage, and the failure only exists inside the service process where the Qt mainloop is the dbus default. **Fix.** Blocking calls need no mainloop: use a private, mainloop-less connection for the read (`dbus.SystemBus(private=True, mainloop=dbus.mainloop.NULL_MAIN_LOOP)`), closed after each poll so the 15s cadence doesn't leak fds. This also stops sharing one connection between two threads, which dbus-python never promised to survive.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
mysticalsoap/aqomui#117
No description provided.