Repository navigation
Conversation
nvidia unregisters and re-registers its backlight across suspend. The OSD watcher kept its fd on the removed actual_brightness node: poll(2) returned POLLERR at once, pread failed with ENODEV, and the loop went straight back into poll. On resume the daemon sat at 100% CPU (about 1.2M reads/s) until restarted. watchBacklight now runs the poll loop per open node and, when a read fails or returns nothing, re-resolves the device with backlightDevice() and reopens it, backing off 1s to 30s while it is missing. A read that succeeds but does not parse still re-arms the notify and keeps the fd. The saved level is restored only at daemon start, and a reopen does not bump the sequence, so resume never flashes the OSD. Note: Fixed: the shell no longer pins a CPU core after waking from suspend on NVIDIA laptops
Member
🦊 mika's reviewIncremental review: new commits since the last checkpoint. Summary
Change diagramflowchart LR
Watcher["Backlight watcher"] --> Node["actual_brightness node"]
Node -->|read failure| Retry["Retry with backoff"]
Retry -->|re-resolve and reopen| Node
Node -->|brightness change| OSD["OSD topic"]
Estimated review effort: 2/5 · risk: low 📁 Walkthrough details
|
An unreadable max_brightness at startup returned from watchBacklight and left the OSD without a watcher for the session. It now enters the reopen loop like a node that went away, and the saved level is restored only when the device was readable at startup.
neur0map
reviewed
Nov 8, 2026
neur0map
left a comment
Member
There was a problem hiding this comment.
🔍 mika reviewed this change; details in the walkthrough comment.
The first read in watchBacklightNode treated errBacklightValue as a dead node, unlike the poll loop. A successful but unparsable read has already re-armed the notify, so the watcher now keeps the fd and waits for the next change instead of closing it and backing off.
This branch has not been deployed
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.
What changed
On a laptop whose backlight is driven by nvidia (
nvidia_0),ryoku-shell daemonsits at 100% CPU after waking from suspend until it is restarted.Cause. nvidia unregisters and re-registers its backlight across suspend, so
/sys/class/backlight/nvidia_0is a new kernfs node after resume. The OSD watcher inipc/osd.gokeeps its fd on the oldactual_brightnessnode. On that dead nodepoll(2)returnsPOLLERRat once,preadfails withENODEV, andwatchBacklightdoescontinuestraight back intopoll. Nothing ever re-arms the notify, so the loop never blocks.Seen on the live daemon (1.0.5, unchanged in
osd.goonunstable-dev):/proc/<pid>/ioshowed about 1.2M read syscalls per second withrcharnot moving, all on one thread, and the node's timestamp matched thePM: suspend exitline.Fix.
watchBacklightNode, which returns once the node is dead: a read that fails or comes back empty.watchBacklightthen re-resolves the device withbacklightDevice()and reopens it, backing off from 1s to 30s while it is missing and resetting after a node that worked.readActualnow returns an error, so a read that succeeds but does not parse keeps the fd: that read already re-armed the notify.seq, so resume does not flash the OSD.Area
ryoku
How it was tested
Arch, Hyprland, laptop with an
nvidia_0backlight.ryoku-shellopens the new node and the daemon drops to 0% CPU, which confirms the stale fd is the cause.go build ./... && go vet ./... && go test ./...inryoku/shell/ipcpasses.TestReadActualClassifiesDeadNode(closed fd and empty read are dead-node errors, an unparsable value iserrBacklightValue) andTestWatchBacklightNodeReturnsOnMissingNode. The sysfsPOLLPRIpath itself cannot be driven from a regular file in a test.Checklist
[area] scope: summarycommit subject.CHANGELOG.mdupdated in the area I touched.--no-verify.luac -p), shell scripts passbash -n, and QML passesqmllintwhere applicable.