Watch
1
0
Fork
You've already forked souveraine
0

fix lock feedback loop + impatient sessiond boot lock

Session.lock() notified logind unconditionally; hypridle's lock_cmd fires
on logind's Lock signal, so every lock echoed back through logind ->
hypridle -> lock() forever. Observed: ~450 lock requests/min for 7 minutes,
re-locking the screen seconds after each unlock. Notify only on the
unlocked->locked edge.

sessiond gave up waiting for the compositor lock ack after one dispatch —
at boot Hyprland defers the ack while the splash holds DRM, so the initial
lock silently failed (journal: 'never acknowledged', 0.7s in) and boot ran
on the legacy lock path. Wait up to 60s for locked/finished.
This commit is contained in:
Fimeg 2026-07-17 06:01:36 -04:00
commit 50530b1159
2 changed files with 26 additions and 11 deletions

View file

@ -247,20 +247,23 @@ pub fn run(
state.lock = Some(lock);
queue.roundtrip(&mut state).context("lock roundtrip")?;
// Wait for the compositor's verdict: exactly one of locked/finished is
// guaranteed by the protocol, but NOT promptly. At boot Hyprland defers
// the ack until it is the active DRM session — and the splash can hold
// DRM for many seconds before its watchdog fails open. One dispatch was
// far too impatient (observed: gave up 0.7s into boot, and the whole
// lock-before-shell guarantee silently degraded to the legacy path).
// Wait up to 60s, dispatching as events arrive.
let verdict_deadline = std::time::Instant::now() + std::time::Duration::from_secs(60);
while !state.locked && !state.finished {
if std::time::Instant::now() >= verdict_deadline {
bail!("compositor never acknowledged the session lock (60s)");
}
queue.blocking_dispatch(&mut state).context("waiting for locked")?;
}
if state.finished {
return Ok(SessionOutcome::Denied);
}
if !state.locked {
// Compositor may answer on a later dispatch; give it one blocking
// turn before declaring failure.
queue.blocking_dispatch(&mut state).context("waiting for locked")?;
if state.finished {
return Ok(SessionOutcome::Denied);
}
if !state.locked {
bail!("compositor never acknowledged the session lock");
}
}
info!("session lock acquired");
let user = auth::session_user().map_err(|e| anyhow::anyhow!(e))?;

View file

@ -314,7 +314,19 @@ Singleton {
// We also notify logind when it is available so other consumers see
// the standard session event. The safe lock does not depend on that
// asynchronous notification returning successfully.
//
// Notify ONLY on the unlocked->locked edge. hypridle's lock_cmd
// fires on logind's Lock signal, so an unconditional
// `loginctl lock-session` here echoes back through logind ->
// hypridle -> this function forever. Observed on the phone: a
// sustained storm of lock requests (several per second, for hours)
// that re-locked the screen seconds after every unlock and chewed
// battery all night.
const alreadyLocked = GlobalStates.screenLocked;
GlobalStates.screenLocked = true;
if (alreadyLocked) {
return { ok: true, status: "already-locked" };
}
if (root.hasLoginctl) {
const notified = root.runVerb("lock", ["loginctl", "lock-session"]);
return notified