Skip to content

feat(bus): wake on NameOwnerChanged instead of polling — #668's cooldown made squatter recovery take up to 5 min #669

Description

@vibechoom

Deferred scope from PR #668 (#653), filed rather than left in a PR body.

The trade #668 made

Before #668, a contested bus name retried RequestName at ~4 Hz forever — 14,400 calls an hour, silently. #668 feeds the Exists | InQueue path into consecutive_losses_to so permanent_after trips, latches, and drops to a cooldown: 12 attempts an hour, each logging once. That is the right fix for the flood.

The cost, stated plainly in that PR: recovery is now slow. If the squatter exits, we no longer notice within 250 ms — we notice on the next cooldown wake, up to ~5 minutes later.

Concretely: if mako or dunst is running when trollshell starts, and you then stop it, notifications stay dead for up to five minutes with nothing indicating why. Before #668 that recovery was near-instant (at the cost of the silent flood). This is not a regression introduced carelessly — it is the same trade the already-shipped PermanentlyTaken path had already made, and #668 merely extended it to the more common contention path.

The fix

Poll-with-cooldown is the wrong shape for "wait until a name is released". D-Bus already pushes that event: org.freedesktop.DBus's NameOwnerChanged signal fires with an empty new_owner when the holder drops the name.

So instead of sleeping out the cooldown, select! on:

  • the cooldown timer (keep it as the backstop — a missed signal must not strand us), and
  • a NameOwnerChanged subscription filtered to the name we want,

and re-attempt immediately when the signal says the name is free. That gives near-instant recovery and keeps the 12/hour ceiling, because the fast path is event-driven rather than polled — the two goals stop being in tension.

hytte-bus already has the primitive: signals(BusKind::…, …) with .signal(…), which is how the rest of the crate subscribes. No new dependency.

Worth checking while in there

Severity

Low. Self-heals either way; the difference is 5 minutes versus instant, in a situation the user created by running a competing daemon. No flood, no crash, no data loss. Worth doing because the primitive is already in the crate and the current shape is a poll standing in for an event that D-Bus already delivers.

Also from #668, already handled — recorded so it is not re-litigated

The PR's first attempt resolved the holder with a best-effort GetNameOwner and used an <unknown> sentinel on failure. That reintroduced the original bug through a different door: GetNameOwner fails systematically on this path, not rarely, because a peer that refuses replacement often releases the name moments later and the broker answers NameHasNoOwner in between. The tally then oscillated between the real name and the sentinel and permanent_after could never trip. It was caught by the pre-existing permanently_taken_after_three_losses test hanging. The shipped code has record_loss take Option<&str>, with an unattributable loss continuing the tally rather than starting a new one, pinned by hermetic tests. Do not "clean up" the Option back into a sentinel.

Refs #668, #653, #634, #646, #429.

Metadata

Metadata

Assignees

No one assigned

    Labels

    code-climateGrounded finding from a code-climate / architecture audit sweep

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions