Skip to content

omamobile-sleepd: notice a resume when logind says so, not at the next poll - #642

Merged
derekross merged 3 commits into
derekross:mainfrom
Somnius:sleepd-event-1110
Nov 11, 2026
Merged

derekross merged 3 commits into
derekross:mainfrom
Somnius:sleepd-event-1110

Conversation

@Somnius

@Somnius Somnius commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

@derekross, next one from the suspend tests on the 8 Pro. After a power-key wake the lock screen took a few seconds to light, and the reason is in omamobile-sleepd: it only notices a resume by polling suspend_stats/success, and the watcher itself is frozen inside its sleep while the phone sleeps. So after the kernel is back it finishes the rest of that sleep first. The poll is 2 s with the toggle on and the screen off behind a secure lock, 5 s otherwise.

Measured with the monotonic journal on the phone (toggle off, so the 5 s branch), one power-key wake after a 58 s s2idle:

kernel "PM: suspend exit" at 85134.758
"omamobile-sleepd: resumed, woken by IRQ 100" at 85139.639
gs-decon back at 120 Hz at 85140.140

So the PIN pad was asked for 4.9 s after the kernel had resumed.

The change: the watcher also listens for logind's PrepareForSleep signal. When it says false (the sleep is over) the watcher gets a USR2 and its wait ends at once, so the loop reads suspend_stats and lights the screen right away. PrepareForSleep(true) is ignored. I used busctl --system --json=short wait because a user service can't use busctl monitor (it's refused for a normal user), and wait is in systemd 257 and later (the phone has 262). wait returns after every message, so the listener subscribes again each time. The poll stays as it was: a raw write to /sys/power/state never sends the signal, and if busctl fails or prints something unexpected the listener stops and says so in the log, then the watcher is on the old poll. The listener and the busctl under it are killed when the watcher stops.

After, through systemctl suspend (so logind sends the signal), final version of the script, about 10 minutes of s2idle, woken with the power key:

kernel "PM: suspend exit" at 89427.464
"omamobile-sleepd: resumed, woken by IRQ 100" at 89427.522
"a power-key wake: lighting the lock screen" at 89427.530
gs-decon 120 Hz at 89428.010

That's 58 ms from the kernel to sleepd noticing, instead of 4.9 s (the first revision of this change gave 3.7 ms on the same test). The screen came on about half a second after the kernel resume, and that part is the display coming back, not sleepd.

Tests: test/sleepd.sh passes (7 groups, all ok). The new group uses a FIFO as the stand-in for logind, and a fake sleep that hangs for real without moving the clock, like the frozen sleep. It checks that a false ends the wait of the sleeping watcher (the run is limited to 10 s and the hang is 20 s), that a true before the false doesn't break the listener, that with no listener the poll still lights the PIN pad and the log says it gave up, and that nothing of the listener is left running after the watcher exits. I ran the same test against the old omamobile-sleepd and the "ends the wait" case fails (timed out).

Not done / limits: there is still a tiny window right before wait where a USR2 can be lost, which just costs one normal poll; same latency as before, not worse. I only tried it on my Pixel 8 Pro, with the sleep toggle off, so the 2 s branch wasn't measured, and a systemd older than 257 has no busctl wait (the listener then stops and logs it).

…t poll

The watcher is frozen inside its sleep while the phone sleeps, so after a
resume it only noticed it, and lit the PIN pad for a power-key wake, when
the rest of that sleep ran out: 4.9 s after the kernel finished resuming on
the Pixel 8 Pro with the toggle off (the 5 s poll), up to 2 s with it on.

Listen for logind's PrepareForSleep(false) with busctl wait (a user
service can't use busctl monitor) and end the wait with USR2 so the loop
reads suspend_stats at once. PrepareForSleep(true) is ignored. The poll
stays as the fallback: a raw write to /sys/power/state never sends the
signal. The listener and busctl under it are killed with the watcher.
Tests: a FIFO stands in for logind, a one-shot real hang for the frozen
sleep; the new case fails on the old script.
@derekross

Copy link
Copy Markdown
Owner

@Somnius thank you, this is the right fix for a delay I had put down to the display. The explanation holds: the watcher is frozen inside its sleep with the rest of the session, so after the kernel is back it sits out what was left of 2 or 5 s before it looks at suspend_stats. Your two journal traces (4.9 s before, 58 ms after) show it plainly.

I checked the parts the PR rests on rather than taking them as read. busctl wait is there since systemd 257 and, unlike call, has no timeout of its own: on my laptop one was still waiting after half a minute and returned the moment its signal came. Its --json=short output is exactly {"type":"b","data":[false]}, which is what your pattern and the test's FIFO use. I ran test/sleepd.sh twelve times in a row (12 of 12), and with stop_listener turned into a no-op the "killed with the watcher" check fails, so that assertion earns its place. Keeping the poll for a raw freeze and for a listener that gives up, with a log line when it does, is the right shape.

What's left is mine to do: this changes when the PIN pad is lit after every resume on every phone, and so far it has run on your 8 Pro with the toggle off. Before I merge I'll run it on my Pixel 7 Pro: a power-key wake with the toggle off and five with it on, and a dark RTC wake to see the 5 s grace start at the resume. The thing I want to see there is omamobile-keys wake arriving while the display is still coming back, which the old delay used to hide. I'll post the journal lines here.

Small things, none blocking:

  • Please name the sender: busctl --system --json=short wait org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager PrepareForSleep. Without it the match takes that signal from any process on the system bus. A fake one would only end a wait early, but it's free to rule out.
  • kill_tree stops busctl before the listener, so on a normal stop the listener can get as far as logging "no more resume events from logind: back to polling" before it is killed. I didn't catch it in my runs; killing the listener first (collect its children's pids before) would close the window.
  • A listener that ends stays ended until the service restarts. A retry after a minute or so would be kinder; fine as a follow-up.
  • Could you add uname -r and the omamobile-shell version to the two traces?

I'll set the package version at merge (omamobile-shell 0.2.119, or 0.2.118 if this goes in before #640), so no need to touch version.

@derekross

derekross commented Nov 11, 2026 •

Copy link
Copy Markdown
Owner

The 7 Pro run is done, 2026-11-10: this branch merged with today's main, installed as omamobile-shell (a test build numbered 0.2.119), on 7.3.0-rc6-omamobile-push1-gc7fb37460f84, on a boot with the modem off.

Five sleeps, five wakes on the power key, each lit on the first press and staying lit:

Sleep How it slept "resumed" after the kernel's PM: suspend exit "lighting the lock screen" after it
1 systemctl suspend from the phone's terminal, toggle off 0.12 s 0.12 s
2 toggle on, power key 0.12 s 0.30 s
3 toggle on, power key 0.10 s 0.18 s
4 toggle on, screen off and locked for 30 s 0.13 s 0.23 s
5 toggle on, power key 0.11 s 0.30 s

suspend_stats: 5 ok, 0 failed. One busctl … PrepareForSleep process throughout, including after the last wake. No UFS error-handler pass, no I/O error, Wi-Fi back after each. So the wait that used to be 2 to 5 s is about a quarter of a second here, and the lock was lit by the time I could look.

Not run: the dark wake from an RTC alarm (the 5 s grace and the second sleep). My alarm didn't get set before the phone slept, so that case is still open; the phone did stay dark and asleep for several minutes and wake on the key. And the stop and start of the unit by hand (the listener going away with it) is covered by your test only.

That was the one thing this was waiting for. The notes from my review are small and can come here or in a follow-up, as you prefer; I set the version at merge.

… busctl

busctl wait now names org.freedesktop.login1, so a PrepareForSleep from
another process on the system bus is not taken for logind's. kill_tree takes
a process's children's pids and kills the process first, so on a normal stop
the listener can't get to its "no more resume events" line because busctl
died under it. The test checks the sender in the arguments.
@derekross

Copy link
Copy Markdown
Owner

@Somnius thank you, both small points are in: the wait names org.freedesktop.login1, and kill_tree takes the listener before its busctl, with the test checking the sender in the arguments. The retry for a listener that ended can be a follow-up whenever you like.

Since the command changed after my Pixel 7 Pro run, I checked the new form with busctl 262 on my laptop: with the sender named it still waits without a timeout of its own (still waiting after 40 s), and the four-argument form delivers a signal. I'll look at the journal after the first sleep on the 7 Pro once this is installed and add the line here; I don't expect a difference.

I'm setting the package version on your branch and merging (omamobile-shell 0.2.120, after #640).

One request outside this PR: your traces here and in #640 are the first suspend results from a Pixel 8 Pro (a 58 s and a 10 minute s2idle through systemctl suspend, woken by the power key), while the ROADMAP's Suspend row still says it has never run. Could your next husky notes PR record them: the kernel release, how s2idle was selected (the package has no sleep drop-in until #656), whether Wi-Fi stayed loaded, and dmesg | grep -E "link startup failed|Host init failed|I/O error" after the wake?

@derekross
derekross merged commit 57dcf17 into derekross:main Nov 11, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants