Conversation
…laced Writing timeout on a live IdleMonitor made it go permanently silent. The notification was recreated correctly, but the replacement is routinely allocated at the address the old one was just freed from, and bNotification is a bindable property, so assigning an equal pointer notified nothing. isIdle binds through bNotification to the notification's own isIdle, so it was never re-evaluated and kept the value it last read. Toggling enabled recovered it only because that is two updates with a real nullptr between them. Clear bNotification before freeing the old notification so both transitions are observable, which also drops the subscription to the old notification before it goes away. The regression test writes timeout on a live monitor and waits for the new notification to report idle. A zero timeout is specified to notify as soon as the seat is inactive, so the test does not have to wait out a real idle period. Both tests skip when no compositor supporting ext-idle-notify-v1 is present.
voglster
marked this pull request as ready for review
September 7, 2026 18:01
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #938.
IdleMonitorgoes permanently silent after a runtime write totimeout. Thenotification is recreated correctly — the interesting part is that nobody is
listening to it.
Cause
updateNotificationfrees the old notification and allocates the replacementwithin the same call, so the allocator routinely returns the block just freed:
bNotificationis only ever written once, at scope exit, and the new pointercompares equal to the old one.
QObjectBindableProperty::setValuereturnsearly on an equal value without calling
notify(), so thebIsIdlebindinginstalled in
onPostReload— which readsbNotificationand then reachesthrough it to that notification's own
bIsIdle— is never re-evaluated andnever subscribes to the replacement. When the new notification receives
idled, nothing propagates.The old
notification = nullptrassigned the local variable, not the property,so the property never observed an intermediate state.
This is why toggling
enabledrecovers a wedged monitor: that path is twoupdateNotificationcalls with a realnullptrwritten to the property inbetween.
Note the old code was only correct when the allocator happened to hand back a
different address. The fix removes the dependency on allocator behaviour rather
than relying on it.
Fix
Clear
bNotificationbefore freeing the old notification, so the propertytransitions
A -> nullptr -> Band both writes notify. It also drops thebinding's subscription to the old notification while it is still alive.
Tests
src/wayland/idle_notify/test/monitor.cppadds two cases. A zero timeout isspecified to notify as soon as the seat is inactive, so neither has to wait out
a real idle period.
freshMonitorReportsIdleis deliberately a real assertion rather than a guard,so a regression in the construction path fails loudly instead of masking the
second test. Both skip when the compositor lacks
ext-idle-notify-v1.Full suite: 10/10 passing in 0.65s.
The bug is allocator-dependent, and so is the test
Built with
-DASAN=ON, the unfixed code passes both tests. ASAN's allocatorquarantines freed memory instead of handing it straight back, so the replacement
lands on a different address,
bNotificationgenuinely changes, and the bindingre-subscribes. That is independent confirmation of the mechanism — remove the
address reuse and the bug disappears without touching
updateNotification.It also means this regression test cannot catch this bug under ASAN. No
black-box test can: without address reuse the old code is correct. Worth knowing
before concluding the test is ineffective if you run the suite sanitised.
ASAN reports nothing on either version, so this is a stale-value bug rather than
a memory-safety one. Clearing the property before the
deleteis defensiveordering, not a use-after-free fix.
Two more things worth flagging, both happy to change:
src/waylandand the first use ofQTRY_*in the tree. It needs a live compositor, so it will skip in CI ascurrently configured — it is a regression test for developers rather than CI
coverage.
protocol but mishandles zero timeouts will fail rather than skip.
Thanks for Quickshell — this is my first contribution here, so please tell me if
I've got the conventions wrong.
I came at this from the other end: my screen stopped blanking on Omarchy and I
went digging for why. It turned out to be this rather than anything in Omarchy's
shell, which is how I ended up in
idle_notify.Full disclosure on process: I used Claude Code to investigate this and write the
patch and tests. I directed the work, reviewed the diff line by line, and had it
walk me through the parts I didn't know — I can explain the reasoning behind
every line, and responsibility for it sits with me, which I understand is what
CONTRIBUTING asks for. Flagging it because I'd rather you know than wonder.
On style: I've run
clang-format, followed the per-moduleqs_testpattern fromsrc/core/test, and matched the commit subject convention. But I write Pythonday to day, so if the C++ idiom is off anywhere, say so and I'll fix it.