Bug: Stop-Process does not issue SIGTERM as expected. #13664
Comments
|
@robertbaker Please check with latest PowerShell 7.1 Preview build. |
|
I guess it is related to .Net API. |
|
This seems to be the case that it jumps straight to Kill instead of requesting close I believe it traces back to here or possible here It seems like you could send CloseMainWindow and since it returns True or False, just fall back to This would enable "soft" closes of something like Notepad that has an unsaved document opened in it, as well as -Force closing the same document if desired. It would also give programs a chance to handle being closed (unless they were Forced) to do things like write save a final recovery copy of unsaved documents, which they won't get if a Kill is sent |
|
@PsychoData Thanks for your investigations! Do you want to pull PR? I'd review and merge. |
|
not sure I would be best for it - this would be my first foray into Posh Core coding and in a pretty core area Not sure if there are any other best practices I should hit for it too, or creating tests for it. If someone else wanted to go for it, I would feel better about it |
|
@PsychoData The change you propose is not complex. I don't think we can create a reliable test for this - I think manual testing the scenario enough (with good comments in code). |
|
well - fine if you twist my arm :) |
|
@iSazonov there's what I threw in |
|
@PsychoData Thanks for your contribution! Original behavior on the cmdlet is silently kill a process. We can not change the default behavior otherwise this will be a breaking change. I mean Update: CloseMainWindow() doesn't block and we can do something like http://csharp-slackers.blogspot.com/2008/09/terminate-process.html In separate PR we could add new parameter like |
|
On further thought, I see that the current proposed implementation is a breaking change. I thing we should avoid a breaking change and follow the original behavior on the cmdlet that is silently kill a process.
For 1:
For 2:
/cc @mklement0 What do you think? |
|
I am not crazy about the Well, if we are going to keep it with force close (Kill) function on and then it could iterate through the Notepad Processes, start the thread timer for the Kill and then send the Close signal. I suppose we could do some testing - but I'm not sure 20 milliseconds would be enough time to the random processes to handle the Close event, close anything down (remember, some of that might be trying to send network session close requests, etc). But that's just deciding on a good default without tying up the timeline for too long. For example, sometimes Outlook can close in a fraction of a second, sometimes it takes 1/2 a second, sometimes a few seconds, or sometimes it hangs for what seems like unlimited amount of time (I've seen hours) a param like |
Yes, I believe it is mandatory requirement to avoid a breaking change. I like the idea about a default value for the new parameter. As for the parameter name, perhaps we could use
It would be again a breaking change for Force parameter (today it allows only to close a process of other users). I believe we could use zero timeout (in GracefullyStopTimeout parameter) for the scenario - it is easy understandable for users than complicating Force parameter. |
|
@iSazonov, I agree regarding the concerns about the breaking changes, but let us take a step back: Conceptually, we are dealing with two modes of termination:
As for default behavior:
This discrepancy is unfortunate, but I think we're stuck with it. In terms of terminology, it is similarly unfortunate that "stopping" a process ( As for synchronous vs. asynchronous behavior: Both modes of termination are inherently asynchronous - they send a signal and return instantly:
Given the above, I think the conceptually cleanest approach would be:
In case a timeout is needed, use Another aside: It is unfortunate that the current
|
|
right, re: the nomenclature, I was usually referring back to the .NET .Kill() method which will force-stop immediately and is somewhat analogous to To keep things clear I'm going to call it On a separate point, it would probably be useful to get some extra information about what kind of terminations it was able to send (
I don't know if there is a way to do the "wait and fall back to SIGKILL" |
|
On the sidenotes, I think enabling the other types of signals to send besides SIGKILL and SIGTERM sound like a great idea, but most likely seem like it ought to be a whole separate cmdlet? Converting the But those should probably be separate issues to talk about those suggestions |
|
@mklement0 @PsychoData Thanks for sharing your thoughts!
.Net does not support signals at all - it is fundamental limitation. We shouldn't go in the direction (until something will be changed in .Net.)
It would confusing users. We use Timeout parameter of int type in some cmdlets. See
This functionality is in Wait-Process. We have no need to move it to Stop-Process. Main question in the issue is should we try to make the cmdlet more smart on Windows so that call CloseMainWindow() before fallback to Kill()? If no this simplify all. Now I think this would be the best way. Can you vote for this? |
|
@iSazonov, I agree regarding the issues not to discuss here; it is exactly why I called them asides: something to perhaps inspire a separate discussion, though I get that that's problematic without actually creating and/or pointing to such separate discussions, because the temptation is there to respond here. To close the one tangent: Point taken re signals in general, but see below. @PsychoData, re nomenclature: I should have made it clearer that I used In terms of implementation,
Yes, we have In other words: For convenience,
I see two basic approaches: Option A: Focus on separation of concerns, as suggested by @iSazonov and also in my previous comment: This means not implementing any fallback logic and not implementing any timeout in You'd get asynchronous behavior by default, as currently, but can opt-in to wait indefinitely with In terms of syntax, this means (for brevity I'm only showing the
That is, to implement a timeout with cooperative termination you'd have to use something like:
This means that in order to fall back to forced termination you'd have to handle that error and call The question is how common this scenario is. If it is common, we should make things easier, in which case my suggestion is to add a Option B: Focus on high-level logic, along the lines of @PsychoData's proposal:
That is, asynchronous behavior would remain the default (also with Only with (The assumption is that I can see arguments for both options; if needing to fall back to forced termination is a common scenario, I can see the appeal of option B. |
|
Well, Originally my thinking was a third, I will call Option C Option C: Have Stop-Process send After all the discussion I see why we wouldn't want to change that functionality of expecting Stop-Process to always result in a process that is my thinking with Option B is that we preserve the high-level function of Option B is a nice medium between the current "Always default to kill everything" and my originally provided code of "Try to send Close, but Kill if .NET says that failed to send" |
In the case this could be separate enhancement since it is not mandatory for enhancement we consider here.
I feel most of users follow intuitively the terms. I believe we need to follow this in parameter names too. If we ask users what is: Stop-Process -Kill
Stop-Process -Terminatemost of them give us right description. But If we start with adding new Terminate switch (and perhaps Kill for symmetric) this will address current issue in simplest way and open ways for future enhancements. |
|
On a meta note, I think at this point it is clear that before implementing anything we need to write up a new, focused proposal, following this discussion. Re
Based on my new proposal below we won't need the
While I like the idea of these contrasting switches to make the two modes explicit, there is the awkwardness of then having a switch being true by default, namely Also:
In short: We won't be able to use existing terminology from one platform without it clashing with that of another. My suggestion was motivated by using names based on platform-neutral abstractions that express the conceptual intent; perhaps If we had established semantics of
We cannot default to cooperative stopping without breaking backward compatibility - users may have come to rely on unconditional, forced, quasi-synchronous termination. Given the conceptual musings above, I sincerely wish we could break backward compatibility, which makes this a candidate for #6745. As for timeouts: For simplicity and predictability, I'd use the timeout as a single, overall waiting period, irrespective of how many processes are targeted: I would start a single timing in the Let me propose Option C:
Note: For consistency, I suggest also making the by-default kill operation synchronous (call
@PsychoData, note that this proposal intentionally does not include a built-in, automatic kill timeout with
|
|
Thanks, @iSazonov, but let me spell out the implications of your proposal, from which I conclude that it is not worth implementing as such:
Note: I am partly out of my depth here, but I hope I'm at least fundamentally correct:
This implies for your proposal that if
Even if we address all the problems above - i.e. if we truly give all all targeted processes a chance to terminate gracefully - enforcing (ultimate) termination should (a) be opt-in and (b) can, as stated, only be achieved by waiting for actual termination based on a timeout, given that |
|
@PowerShell/powershell-committee reviewed this and agrees to not make a breaking change where automation will expect processes to be killed. .NET currently does not provide a way to send |
I believe we should do the cmdlet too smart and complex. The suggestion is to just add such a feature - just send the signal and nothing else. All other smart things the user can do himself (or we can add later after receiving feedback). We can implement this on Unix too https://stackoverflow.com/questions/41041730/net-core-app-how-to-send-sigterm-to-child-processes
|
|
Thanks, @iSazonov - good find, and I do think that starting small is an option, but let me flesh your suggestion out to see its full implications:
Either way (after either failing or succeeding to send
To detect whether termination occurred, a separate When use of
This means that console applications launched from a shell can NOT be targeted (such as
If everyone agrees that this - initially minimal - functionality is still beneficial and the implications are understood and well-documented, I think it's worth doing. |
|
@mklement0 I think it makes no sense for us to try to do something too clever since not even the platforms themselves do it. (Moreover, there are differences in async/sync behavior.) if (Graceful.Present && TryStopProcessGacefully())
{
// return;
}
else
{
process.Kill();
}
...
void TryStopProcessGacefully()
{
#if UNIX
SendTerminateSignal(process);
return true;
#else
return CloseMainWindow();
}For reference - Process.Kill() on Unix to implement SendTerminateSignal() https://source.dot.net/#System.Diagnostics.Process/System/Diagnostics/Process.Unix.cs,57 |
I was proposing the very opposite:
In the success case - being able to send the signal / close message - the uncertainty over whether that signal / message will eventually, possibly asynchronously be honored is built into both mechanisms. Not trying to resolve this uncertainty through superimposed logic is the gist of the previous proposal. Users who care about the eventual outcome must use follow-up commands, as described, at least for now. The only challenge I see is to make users understand the limitations of what processes can be targeted on Windows. |
|
@iSazonov: Sorry, I misread your previous comment: there is a fundamental disagreement here: I think we should not fall back to killing, for the reasons stated. Later, we can implement superimposed high-level logic, through additional parameters. |
|
The only reason that we started talking about sending SIGterm, but then maybe waiting for a time out of some sort and then sending sigkill was that way we could preserve the high-level functionality that the processes will be stopped once the cmdlet is done running. Or if there was some error, throw an exception/error. There should probably be some option to just send the close event without sending the kill, but foremost we should preserve the existing functionality so we don't break current deployments. I really don't think that using a separate wait-process or synchronously waiting with .WaitForExit would be a good idea at all, because that process doesn't have a way to short circuit out if it is taking 20 minutes to close. I looked at the code in the dotnet core clr , and there is definitely going to be no benefit to this on Unix currently, but windows still could benefit. I'll try to hack together some code to demo the functionality, because I feel like all of these other extensions that have been discussed could certainly be useful, but the feature bloat from the original goal is significant. If the dotnetCLR finally gets updated to have support for sending more types of events (preferably including arbitrarily sending whichever signal we want - like the Unix kill command) then that seems like it would be the time to revisit this and abstract the sending signals to some other function possibly. In the meantime, sending closeMainWindow would be sufficient for many Windows services, tray agents, and other processes the gracefully close themselves rather than having to be killed, and it is the closest option that we have |
That's the current proposal: I definitely would like to see the high-level functionality of ensuring that the process is stopped, but the above would be a fairly simple and straightforward start whose behavior doesn't deviate from the underlying system mechanisms. If As for a (possibly later) enhancement that builds on the above: First, I agree that general signal support should be a separate discussion. I'm always a fan of desired-state functionality, but I believe it should be opt-in here, to modify the underlying system behavior on demand. If you're asking a process to terminate, it is not a given that your intent is to kill it, if it refuses to / doesn't terminate within a given timeout.
Of course, using a timeout makes sense to prevent infinite waiting. But users should have a choice as to:
If we do go with a default timeout (and I have no idea what period would make sense, but it definitely must be clearly documented), it should only apply if you've opted into fallback-to-killing behavior, with a switch. That leads me to Options D and E: What they share:
Option D: with a default timeout for the fall-back-to-killing opt-in:
That is, (Unlike without the opt-in, Option E: without a default timeout: This forces users who want to ensure ultimate termination after request-based termination fails to specify a timeout explicitly - though I can see how that would be cumbersome if a reasonable default timeout can be provided.
|
|
Actually I missed that the current behavior actually does make an attempt to gracefully terminate some processes, namely (by definition Windows-only) services (though note that the conditional is not platform-specific and tests just by process name): (As an aside: the comment describing the ) That said:
|
The existing mechanisms are:
Once you superimpose (request-termination-then-)fallback-to-kill logic, you get quasi-synchronicity even if you don't call And, of course, if the process terminates within the timeout period, you have synchronous behavior by definition. To put it differently: what you're invariably looking for is synchronicity with respect to knowing that actual termination will occur (was successfully initiated), which, if a termination request is first sent invariably involves waiting.
Agreed - that's why I said we're stuck with the behavior. Also, just to remind us, the committee has already turned down any enhancement here, so this may never happen or at least not anytime soon. I'd say the only chance for this to be revisited is if we agree on a way forward that also addresses the committee's concerns, and present that in a new, focused feature-request issue. The committee's concerns were:
Do I understand correctly that you want to bake the request-first-then-fallback-to-kill logic into Even though I can see the appeal of this from the perspective of trying to terminate gracefully while ultimately ensuring termination, it does constitute a breaking change:
Even if everyone were comfortable with this change, you would then need a switch such as |
|
I don't know - I just tried to kick something that seemed like a good idea along. From the very beginning I was saying I didn't think I was best for this because I knew it would be a breaking-ish change and there would likely need to be considerations for it to preserve the But it would be very useful for any program that properly handled a Close request, and give IT a MUCH easier way to gently close processes, without them. For example, if I wanted to issue a restart - nothing would stop me from saying Someone else can try to chase this down if they want, but my effort to get this done is though, because my skill level was spent before I ever made the PR when @iSazonov was pushing me to, exactly like I said it would be. |
I mean if a process doesn't implement SIGTERM handler a stopping behavior will be like SIGKILL. |
|
You're right, but that means that on Unix the On Windows it shouldn't be reached, at least not by default, because that would mean superimposing destructive logic on the underlying system behavior. With an opt-in such as If the intent is for To summarize:
|
Agreed - I do think we should provide this functionality, but the tricky part is how. Thanks for the discussion; even if no immediate action follows, I think it was useful to get clarity.
Note that it's perfectly fine to only contribute conceptually to a discussion, without being the implementer or needing to know all technical details. I hope that it's clear that the sticking point here is the up-front conceptual work - agreeing on the end-user experience and assessing backward-compatibility concerns - and that just happened to turn out much more complex than originally anticipated. |
|
@mklement0 I think we all are in consensus that we don't change the default behavior of the cmdlet and we all find the new Graceful option being useful. |
That is definitely an option - we can keep desired-state logic out of the cmdlet for now, and possibly enhance later.
No, I don't think so, because by default there should be no fallback to killing - just like In terms of your snippet, this means: if (TryGraceful.Present)
{
if (! TryStopProcessGacefully())
{
// Report non-terminating error along the lines of (obviously needs polishing):
// "Graceful termination not possible (the process either doesn't support it at all (no message loop)
// or cannot process messages in its current state); to kill the process, call without -TryGraceful"
}
}
else
{
process.Kill();
}Again, desired-state logic is desirable, but falling back to killing only if the close-request cannot even be sent makes for half of an ensured-termination feature: a successfully sent request may still result in non-termination, which means that you haven't ensured termination overall. A proper ensured-termination feature would require waiting for termination (if the request was successfully sent, otherwise you can kill instantly), which introduces the need for a timeout (at least a default one, but ideally also a user-specifiable one). (Note that on Unix the case where In terms of syntax, this means:
|
|
@mklement0 If on Windows we can detect whether a process can handle a close event (CloseMainWindow() returns false), on Unix we cannot detect this for SIGTERM. (If we haven't permissions we will get an exception in any case.) I'd prefer to have unified behavior for all OS-s. It is first argument to do not throw and just send a close event. |
Sending a close event: yes. If the system tells you that it cannot, you report a non-terminating error (rather than throwing), just as you would with a permissions problem with This amounts to unified behavior with respect to the underlying system capabilities.
They may care about being able to request graceful stopping, but leaving it up to the target process to comply (see below). On Unix, the processes almost always comply - from what I can tell, even GUI applications such as That is, on Unix, where only truly exceptional conditions (such as lack of permissions) prevent sending the signal, sending On Windows, you're much more likely to run into non-termination:
In the case of (a), you deserve to know that graceful termination is fundamentally impossible (an unfortunate limitation of Windows) - this is what In the case of (b), the only assurance you have is that the signal was "sent" - and there's a definite chance that termination will not occur. If you implement a fallback to killing just for (a), you introduce an awkward asymmetry that additionally hides a system capability: to request termination without enforcing it. Conversely, you have not ensured overall that termination will take place. The only unified platform-neutral, desired-state behavior that is worth providing is the one that would (a) require opt-in via |
|
You seem to be ignoring the fact that the "grace stopping" is only a function of the application and it works only as the developer implemented it. There can be an infinite number of implementations. There's not even precise feedback (from the API) even on Windows. Moreover, there is not even a predefined semantics, even on Windows - the developer can assign any action to the event. There is no point in looking for something in common and trying to make a universal solution. |
That's what we can and should do in the simplest case ( If sending the signal / calling (If sending succeeds, we're done and move on, as you suggest,) As stated, we could do this in a first step, and implement ensured-termination logic (see below) later, if ever.
No, as proposed, there is something we can do: If requested, we can ensure that the process terminates by waiting for termination - whether with a default or specified timeout period - and if termination hasn't occurred within that timeout - for whatever reason (we can't know) - kill the process then. Of course, anyone can implement that logic themselves using the existing capabilities (assuming |
|
It sounds like the only sticking point is what to do if
I find B problematic, but I can also see the appeal of its pragmatism - though it's important to understand that the kill fallback will not apply to GUI applications that accept the message and then pop up a confirmation dialog. If everyone is comfortable with it, so be it. |
Yes. It is only about Windows.
And yes, and no. We could do something smart if CloseMainWindow() did something smart. But see the method implementation: This method does nothing smart. It fast return false if no main window is or it is disabled - why do we need to write an error if such application makes no distinction between killing and grace stopping? For such application killing is the same as grace stopping. So on all OS-s the result is unpredictable - a script write is forced to always check the result he expects in his particular scenario - no errors help.
|
Because you may choose not to kill if graceful termination isn't possible. Consider this scenario: Notepad is open with an unsaved file, and a modal dialog happens to be displayed in it: even though graceful termination is possible with a GUI application such as Notepad in principle, in this case With your proposal you'll get reliable termination only when If it returns By contrast, what I proposed would amount to It is then up to the user to decide whether killing is appropriate.
We can do something predictable that ensures a desired outcome, namely reliable termination - covering both the cannot-send-signal and signal-sent-but-process-didn't-terminate scenarios, as previously described. It would be a convenience feature that compensates for the lack of predictability of the underlying system features. As stated, it requires additional parameters and a more complex implementation. |
|
Let me try to bring closure to this by summarizing our options:
|
|
To get a consistency on all platforms we could not use |
|
If you really wanted to do that, you could simply ignore However, to me that's not consistency - that's just hiding an error condition from the user, given that he intent of requesting termination could not be fulfilled. |


Steps to reproduce
Without moving mouse, tray icon disappears from tray immediately.
Without moving mouse, tray icon is stays visible. Additionally, starting the process again will display a duplicate icon, the old one disappears when hovering over it.
Expected behavior
Stop-Process by default should be SIGTERM, graceful.
SIGKILL should only be used after timeout or -FORCE is used.
Actual behavior
The old tray icon stays visible because app is killed. (This is not actually PowerShell specific, but a technical caveat when processes are killed.)
Workaround
Use taskkill in place of stop-process for processes that have tray icons.
Environment data
The text was updated successfully, but these errors were encountered: