Repository navigation
Conversation
|
Tagging subscribers to this area: @eiriktsarpalis, @jeffhandley |
danmoseley
left a comment
There was a problem hiding this comment.
This seems right. HasExited may end up calling EnsureState(State.Associated), but that does no real work; it may also end up calling UpdateHasExited() and from that point it's pretty hard to reason about whether that might cause a problem, but given the name of the method, it's reasonable to assume it's hardened against the process having exited 😊
|
@dotnet/runtime-infrastructure possibly transient error in dotnet-install on two OSX legs. (curl works for me) I"ll rerun. |
|
@danmosemsft looks like there is a big spike in SDK install failures in last 24 hours: 49 total across the dotnet org. |
There was a problem hiding this comment.
What happens if the process exits after checking HasExited and before accessing ProcessName?
There was a problem hiding this comment.
That's a valid question! Is it worth just wrapping this in a try/catch then, or is that unlikely enough to not be a concern?
There was a problem hiding this comment.
Where is the problematic exception coming from? i.e. what's the stack of the exception that's thrown if the process has exited?
There was a problem hiding this comment.
@stephentoub it's coming from the call to ProcessName which then calls EnsureState ( https://github.com/dotnet/runtime/blob/098fdc5fe634067283369bbb51e7c439431e4105/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs#L512) - the state flag it uses means it'll call EnsureState again, this time checking that it has a non-exited process ID (https://github.com/dotnet/runtime/blob/098fdc5fe634067283369bbb51e7c439431e4105/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs#L951). That's where the exception gets thrown.
There was a problem hiding this comment.
That's where the exception gets thrown.
Hmm, are you sure? It's passing in State.HaveProcessInfo, which doesn't include the bits for HaveNonExitedId, so I would not expect it to be making a call to EnsureState(State.HaveNonExitedId). Rather, I'd expect the exception is coming from here:
with GetProcessInfo returning null and the call then explicitly throwing as a result (the error string thrown also matches the text I see in the exception.
Assuming that's true, then I think I'd prefer to see ToString just rewritten to not use ProcessName at all and avoid the throwing altogether:
public override string ToString()
{
string result = base.ToString();
if (Associated)
{
_processInfo ??= ProcessManager.GetProcessInfo(_processId, _machineName);
if (_processInfo is not null)
{
string processName = _processInfo.ProcessName;
if (processName.Length != 0)
{
result = $"{result} ({processName})";
}
}
}
return result;
}
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
@iSazonov I think, considering I need the base.ToString() in both results, it's fine if I call it in the beginning.
There was a problem hiding this comment.
@stephentoub I feel a little bit "bad" setting _processInfo directly from a ToString() method, though. My thinking was the !HasExisted check would be good enough, since it then looks at if ((state & State.HaveProcessInfo) != (State)0). That way the processInfo gets set in the same place you'd expect it to.
Happy to go either way, though.
But yeah, if it exists immediately after that check, maybe it makes sense to do a try/catch.
There was a problem hiding this comment.
I've gone ahead with the changes, and rebased while I'm at it.
In cases where a process is killed, and ToString is called after that, an exception is thrown. This was due to the use of ProcessName which calls EnsureState - which in turn throws an exception. Instead, we now do a check for the state and fallback to the original ToString. Fix #1520
| [Fact] | ||
| public void TestToString_OnExitedProcess() | ||
| { | ||
| var p = Process.Start("dotnet"); |
There was a problem hiding this comment.
I am not sure we can be certain that "dotnet" is on the path for everyone running this test. I would follow the pattern of other tests, which is to use Process process = CreateProcessLong();. (This just makes a sleeping process) something like
var p = CreateProcessLong();
var name = p.ProcessName;
Assert.Equal($"System.Diagnostics.Process ({name})", p.ToString());
p.Kill();
Assert.Equal("System.Diagnostics.Process", p.ToString());?
There was a problem hiding this comment.
otherwise looks good to me. thanks!
|
rerunning CI |
|
Hello @danmosemsft! Because this pull request has the p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (
|
| { | ||
| // Handle cases where a process would exit straight after our checks | ||
| // and/or make ProcessManager throw an exception. | ||
| } |
There was a problem hiding this comment.
Is this try/catch still needed? What might throw here if the process exits?
There was a problem hiding this comment.
@stephentoub ProcessManager.GetProcessInfo can still throw an exception, IIRC - that's why I left the catch block there
| } | ||
|
|
||
| [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] | ||
| public void TestToString_OnExitedProcess() |
There was a problem hiding this comment.
Just curious, does this test fail on master branch? With .NET5, this program:
using System;
using System.Diagnostics;
class Program
{
static void Main()
{
Process p = Process.Start("bash", "-c \"sleep 100000\"");
Console.WriteLine(p.ProcessName);
p.Kill();
Console.WriteLine(p.ToString());
}
}displays:
bash
System.Diagnostics.Process (bash)
on Linux and macOS, with return code 0.
There was a problem hiding this comment.
@am11 sorry for the delay - yes, the above looks right, in the test we do:
Assert.True(p.WaitForExit(WaitInMS));
In your example, the behaviour is expected, because when you call .ToString() the process is actually still there and the runtime is able to get the information - hence no exception and the information in the output.
There was a problem hiding this comment.
@avodovnik, ah I missed the WaitForExit. With Process.Start("bash", "--help"); and WaitForExit(10_000), it gives:
Unhandled exception. System.InvalidOperationException: Process has exited, so the requested information is not available.
at System.Diagnostics.Process.EnsureState(State state)
at System.Diagnostics.Process.get_ProcessName()
however, with subprocess Process.Start("bash", "-c \"sleep 1\"") and WaitForExit(10_000), it strangely returns subprocess name instead:
sleep
System.Diagnostics.Process (sleep)
(and without WaitForExit, it returns bash in place of sleep as we've seen above)
does this PR change/improve this subprocess behavior?
There was a problem hiding this comment.
Yes, the PR follows our own guidelines on not throwing exceptions in ToString and instead falls back to the default value without displaying the process name. As for the sleep case, the returned name is a result of whatever the underlying process manager returns for that PID, and I believe sleep is correct - it is, in fact, the current process running under that id.
|
Hello @avodovnik have you been able to look at the last comments here? would be great to get this done and merged. |
|
Ping @avodovnik , this has been stale for a couple of months. Any chance you can take a look at the latest feedback? |
Yeah, I will try getting back to it tomorrow or day after! |
carlossanlop
left a comment
There was a problem hiding this comment.
LGTM as it is.
Thanks for making this change, @avodovnik, and thanks @am11 for your review too.
carlossanlop
left a comment
There was a problem hiding this comment.
@avodovnik actually, the CI is old and the results are gone already. Can you please rebase your PR to the latest bits in main so we can ensure the unit tests are passing?
|
As far as I know, close/reopen does this for you.. |
|
I spent most of this morning trying to figure out if my rebase broke the build or it was already broken in |
CI should automatically merge with |
carlossanlop
left a comment
There was a problem hiding this comment.
@avodovnik the CI finished. The failures are caused by the new unit test, and seems to be affecting all platforms. Some examples:
Libraries Test Run release coreclr Linux x64 Debug
Discovering: System.Diagnostics.Process.Tests (method display = ClassAndMethod, method display options = None)
Discovered: System.Diagnostics.Process.Tests (found 253 of 311 test cases)
Starting: System.Diagnostics.Process.Tests (parallel test collections = on, max threads = 2)
System.Diagnostics.Tests.ProcessStartInfoTests.ShellExecute_Nano_Fails_Start [SKIP]
Condition(s) not met: "IsWindowsNanoServer"
Linux
System.Diagnostics.Tests.ProcessTests.TestToString_OnExitedProcess [FAIL]
Assert.Equal() Failure
↓ (pos 26)
Expected: ···.Diagnostics.Process
Actual: ···.Diagnostics.Process (dotnet)
↑ (pos 26)
Stack Trace:
/_/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs(477,0): at System.Diagnostics.Tests.ProcessTests.TestToString_OnExitedProcess()
Libraries Test Run release coreclr windows x86 Release
Discovering: System.Diagnostics.Process.Tests (method display = ClassAndMethod, method display options = None)
Discovered: System.Diagnostics.Process.Tests (found 253 of 277 test cases)
Starting: System.Diagnostics.Process.Tests (parallel test collections = on, max threads = 2)
System.Diagnostics.Tests.ProcessStartInfoTests.ShellExecute_Nano_Fails_Start [SKIP]
Condition(s) not met: "IsWindowsNanoServer"
System.Diagnostics.Tests.ProcessTests.TestToString_OnExitedProcess [FAIL]
Assert.Equal() Failure
� (pos 26)
Expected: úúú.Diagnostics.Process
Actual: úúú.Diagnostics.Process (dotnet)
� (pos 26)
Stack Trace:
/_/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs(477,0): at System.Diagnostics.Tests.ProcessTests.TestToString_OnExitedProcess()
Libraries Test Run release mono OSX x64 Debug
Discovering: System.Diagnostics.Process.Tests (method display = ClassAndMethod, method display options = None)
Discovered: System.Diagnostics.Process.Tests (found 238 of 311 test cases)
Starting: System.Diagnostics.Process.Tests (parallel test collections = on, max threads = 12)
System.Diagnostics.Tests.ProcessStartInfoTests.ShellExecute_Nano_Fails_Start [SKIP]
Condition(s) not met: "IsWindowsNanoServer"
System.Diagnostics.Tests.ProcessTests.TestToString_OnExitedProcess [FAIL]
Assert.Equal() Failure
↓ (pos 26)
Expected: ···.Diagnostics.Process
Actual: ···.Diagnostics.Process (dotnet)
↑ (pos 26)
Stack Trace:
/_/src/libraries/System.Diagnostics.Process/tests/ProcessTests.cs(477,0): at System.Diagnostics.Tests.ProcessTests.TestToString_OnExitedProcess()
/_/src/mono/System.Private.CoreLib/src/System/Reflection/RuntimeMethodInfo.cs(378,0): at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
Darwin
System.Diagnostics.Tests.ProcessTests.TestProcessRecycledPid [SKIP]
Condition(s) not met: "IsStressModeEnabledAndRemoteExecutorSupported"
System.Diagnostics.Tests.ProcessTests.CanBeFinalized [SKIP]
Condition(s) not met: "IsPreciseGcSupported"
Finished: System.Diagnostics.Process.Tests
@avodovnik Let us know if you need any help debugging the test failures. Thanks! |
|
@avodovnik There is now a merge conflict too. I'm going to mark this PR as a Draft for the time-being, but please let us know if you want some help getting this over the finish line. Thanks! |
|
Draft Pull Request was automatically closed for inactivity. Please let us know if you'd like to reopen it. |
In cases where a process is killed, and
ToStringis called after that, an exception is thrown. This was due to the use ofProcessNamewhich callsEnsureState- which in turn throws an exception. Instead, we now do a check for the state and fallback to the originalbase.ToString.Fixes #1520