-
Notifications
You must be signed in to change notification settings - Fork 7.4k
Description
Microsoft PowerToys version
0.94
Installation method
Dev build in Visual Studio
Area(s) with issue?
Awake
Steps to reproduce
The bug
Awake allows the user to bind its lifetime to the parent process. This lets the user keep the system awake while an application or script is executing a long-running piece of work.
The console parameter is --use-parent-pid
.
The current Awake code does not respond correctly if there was an error during lookup of the parent process ID (PID).
The end result is that no console is opened (because of the use of the --use-parent-pid
parameter), and the tray icon is non-functional. Awake has also not set a keep-awake mode at the time the error occurs, so is non-operational, even though the presence of the tray icon would suggest it is active. Closing the parent process will not quit Awake. It may only be stopped by killing its process in Task Manager. Attempting to run another Awake instance will also fail because a Mutex lock prevents this.
Steps to reproduce
Change the GetParentProcess()
method in Manager.cs
to simulate an error while retrieving the parent PID:
private static Process? GetParentProcess(IntPtr handle)
{
return null;
}
Root cause
There are several flaws which combine to produce this bug:
- Premature tray icon creation:
Program.Main
callsTrayHelper.Initialize
before any command-line arguments are parsed or validated. This creates the icon immediately with a default, generic icon. This will lead the user to believe Awake is active when it is actually in an unresponsive 'zombie' state. - Console allocation doesn't happen: The Awake console shows information about the start parameters of the application and allows the user to close it by just closing the window. Unfortunately, this console isn't allocated when
--use-parent-pid
is used. - Silent failure path:
HandlePidBinding
contains the silent failure path. IftargetPid
resolves to0
(such as if the parent process cannot be found), theelse
block is executed, which just writes a log message. At this point no keep-awake state is set. Because the monitor thread has already been started, Awake will continue running forever. - The user is not informed of the error: The console has not been allocated, so the logging of the error only happens to the log file. It is never written out to a console for the user to see.
- Lack of state update: Because none of the
Manager.Set...
methods is called, the tray icon is never updated to an active state, like "Indefinite" or "Timed". The generic icon remains. - Mutex lock: The
Mutex
lock prevents multiple instances from being active, but in this instance, also prevents the user from superseding the faulting process with a new valid one.
In summary: the application launches, shows a default icon, fails silently in the background, and then hangs. The application state is unrecoverable.
Code breakdown
Here's the flow showing the fault:
- No console opened if
--use-parent-pid
is used. InHandleCommandLineArguments
:
if (pid == 0 && !useParentPid)
{
Logger.LogInfo("No PID specified. Allocating console...");
AllocateLocalConsole();
}
else
{
Logger.LogInfo("Starting with PID binding.");
_startedFromPowerToys = true;
}
- Monitor started which never exits. The
Manager
's monitor is started, which handles state transitions, and a thread is started which waits for exit:
Manager.StartMonitor();
EventWaitHandle eventHandle = new(false, EventResetMode.ManualReset, PowerToys.Interop.Constants.AwakeExitEvent());
new Thread(() =>
{
WaitHandle.WaitAny([eventHandle]);
Exit(Resources.AWAKE_EXIT_SIGNAL_MESSAGE, 0);
}).Start();
- Error not followed by an exit: Later in the same method, this code is run, which calls
GetParentProcess()
, settingtargetPid
to0
if it returnsnull
. This logs the error, but significantly, does not exit Awake:
int targetPid = pid != 0 ? pid : useParentPid ? Manager.GetParentProcess()?.Id ?? 0 : 0;
if (targetPid != 0)
{
Logger.LogInfo($"Bound to target process: {targetPid}");
Manager.SetIndefiniteKeepAwake(displayOn, targetPid);
RunnerHelper.WaitForPowerToysRunner(targetPid, () =>
{
Logger.LogInfo($"Triggered PID-based exit handler for PID {targetPid}.");
Exit(Resources.AWAKE_EXIT_BINDING_HOOK_MESSAGE, 0);
});
}
else
{
Logger.LogError("Not binding to any process.");
}
GetParentProcess()
returns null on error: This is the original code we amended before to simulate the error:
private static Process? GetParentProcess(IntPtr handle)
{
ProcessBasicInformation pbi = default;
int status = Bridge.NtQueryInformationProcess(handle, 0, ref pbi, Marshal.SizeOf<ProcessBasicInformation>(), out _);
return status != 0 ? null : Process.GetProcessById(pbi.InheritedFromUniqueProcessId.ToInt32());
}
Suggested solutions
Exit immediately with a non-0 exit code. Exiting the process clears up the tray icon and long-running process.
Priority and severity
This is a crash bug, but should be relatively rare.
✔️ Expected Behavior
When the parent process ID cannot be found, Awake exits immediately. Ideally, the application would also show an error message to alert the user to the issue.
❌ Actual Behavior
An error message is logged to the log file only. Awake does not set a keep-awake mode, leaving the existing power state active. The tray icon is shown, but is non-responsive. The application can only be closed by killing the process in Task Manager.
Additional Information
No response
Other Software
No response