You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Concurrent SetKeepDisplayAwake and SetRequest lose one change: the request is read and written under separate locks (the #30 race, outside Toggle) #39
KeepAwakeController.SetKeepDisplayAwake reads the current request through the Request getter, which takes and releases gate, and then passes a modified copy to SetRequest, which takes gate again (NoSleep/KeepAwakeController.cs:132-133, getter at 88-97, SetRequest at 142-168):
The read-modify-write isn't atomic, so concurrent changes to the request can overwrite each other. The class doc (line 15) promises "Every mutation is serialised". #30/#31 fixed exactly this shape for Toggle by moving the decision inside the lock (SetActive(Func<bool,bool>)), but the request path still has it.
Failure scenario
Start with the request { KeepDisplayAwake = false, Reason = default }.
Thread A calls SetKeepDisplayAwake(true) and reads {false, default}.
Thread B calls controller.SetRequest(controller.Request with { Reason = "Rendering" }) and writes {false, "Rendering"}.
Thread A writes {true, default}.
B's reason is lost, yet StateChanged fires for both calls, so a listener believes both took effect. If B wins instead, A's display setting is lost and the screen blanks even though the caller asked for it to stay lit.
In a stress repro built from the repository's sources with a no-op blocker, 4058 of 20000 iterations ended without both changes applied.
The NoSleep tool itself is unlikely to hit this, because --reason is applied at startup and menu clicks arrive on the UI thread. Library consumers that drive the controller from more than one thread will hit it.
Suggested fix
Do what #31 did for Toggle: add an overload, SetRequest(Func<SleepBlockRequest, SleepBlockRequest> update) (public or private), that computes the new request inside lock (gate), and route SetKeepDisplayAwake through it:
Priority: Medium. The class docs promise that every mutation is serialised, and a stress repro loses an update in roughly 20% of iterations. The NoSleep tool itself is unlikely to hit this, because --reason is applied at startup and menu clicks arrive on the UI thread. So the impact falls on library consumers who drive KeepAwakeController from more than one thread, not on tool users.
Activity
matt-edmondson commentedon Nov 2, 2026
Triage
--reasonis applied at startup and menu clicks arrive on the UI thread. So the impact falls on library consumers who driveKeepAwakeControllerfrom more than one thread, not on tool users.NoSleep/KeepAwakeController.cs:SetKeepDisplayAwake,SetRequestand theRequestgetter. Whoever fixed Two concurrent Toggle() calls on KeepAwakeController can leave it active and both return true: the read and the flip happen under separate locks #30/Decide and apply a Toggle under one lock, so concurrent toggles each flip once [patch] #31 should take it, since the fix (decide insidelock (gate)) has the same shape.Toggleonly). Toggling "Keep display awake too" (or changing the reason) while active leaves NoSleep reporting "awake" with no inhibitor held when the platform refuses the new request #10 touches the sameSetRequestpath but describes a different failure: the platform refusing a new request while active. A fix for either should keep the other in mind. None of the open NoSleep PRs (Skip a non-executable PATH match when looking up a helper [patch] #35, Refuse an Acquire that was queued behind Dispose [patch] #36, Add CompatibilitySuppressions.xml for the Polyfill 11.4.5 shim drift [patch] #38) cover it.SetRequest(Func<SleepBlockRequest, SleepBlockRequest>)that computes the request under a single acquisition ofgate, and routeSetKeepDisplayAwakethrough it. Also add a parallel stress test modelled on the Decide and apply a Toggle under one lock, so concurrent toggles each flip once [patch] #31Toggletest that asserts both changes survive.Generated by Claude Code