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.
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
RequestNameat ~4 Hz forever — 14,400 calls an hour, silently. #668 feeds theExists | InQueuepath intoconsecutive_losses_tosopermanent_aftertrips, 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
PermanentlyTakenpath 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'sNameOwnerChangedsignal fires with an emptynew_ownerwhen the holder drops the name.So instead of sleeping out the cooldown,
select!on:NameOwnerChangedsubscription 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-busalready has the primitive:signals(BusKind::…, …)with.signal(…), which is how the rest of the crate subscribes. No new dependency.Worth checking while in there
RequestNameattempt, or a release landing between the attempt and the subscription is missed and we fall back to the 5-minute path. This is the same class as fix(bus): property tracking calls Get before subscribing — changes in the window are silently lost #429 ("property tracking callsGetbefore subscribing — changes in the window are silently lost"), which is closed; check what that fix did and mirror it.notifications, tray,DisplayConfig, screensaver, bluetooth,Control), so the subscription must filter by name or every release wakes every waiter.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
GetNameOwnerand used an<unknown>sentinel on failure. That reintroduced the original bug through a different door:GetNameOwnerfails systematically on this path, not rarely, because a peer that refuses replacement often releases the name moments later and the broker answersNameHasNoOwnerin between. The tally then oscillated between the real name and the sentinel andpermanent_aftercould never trip. It was caught by the pre-existingpermanently_taken_after_three_lossestest hanging. The shipped code hasrecord_losstakeOption<&str>, with an unattributable loss continuing the tally rather than starting a new one, pinned by hermetic tests. Do not "clean up" theOptionback into a sentinel.Refs #668, #653, #634, #646, #429.