Dispose (or Close) killed process - c#

Calling process.Dispose() or process.Close() (no matter which one, 'cos .Close is called inside .Dispose implementation) after process.Kill() sometimes hangs application.
I can't reproduce this bug stable, but sometimes when WaitForExit finishes by timeout passed to it, application hangs on process.Close() command.
Please suggest me, what the cause of this problem may be?
Note:
I've seen similar question. But there are not answers & upvoted comment says, that the cause of the problem perhaps at details, which are not provided at that question. So I added more details.
I've also seen a linked solution, but I can't use ProcessStartInfo.UseShellExecute = true; 'cos I need to redirect output.
Sorry for verbose code, but I pass it such way, 'cos at similar unanswered question commentators noticed, that not enough details provided (as I noted above)
private static async Task<int> RunMethod(string processArguments)
{
// 1. Prepare ProcessStartInfo
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.Arguments = processArguments;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = false;
// 2. Create process inside using-block to be disposed
using (var proc = new Process())
{
proc.StartInfo = startInfo;
// 3. Subscribe output streams handlers
proc.OutputDataReceived += (sender, outputLine) => { HandleMessage(outputLine); };
proc.ErrorDataReceived += (sender, errorLine) => { HandleMessage(errorLine); };
// 4. Start process
if (!proc.Start())
{
proc.Close();
return -1;
}
// 5. Start the asynchronous read of the standard output stream.
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
// 6. Waiting process to exit with timeout on threadpool to not block UI thread
// Re#er warns me "captured variable `proc` is disposed in the outer scope". But I think it's Ok, 'cos we're awaiting this task inside using block (in next line)
var waitingProcessTask = Task.Run(() => proc.WaitForExit(TIMEOUT), _cancelToken);
bool hasExited = await waitingProcessTask;
// 7. Stop reading streams
// Not sure, these 2 CalncelXxxRead methods are needed. But hope it won't hurt at least
proc.CancelErrorRead();
proc.CancelOutputRead();
// 8. If !hasExited (i.e. TIMEOUT is reached) we kill the process
if (!hasExited)
{
Logger.Debug("0. Before Kill()");
proc.Kill();
proc.Refresh(); // not sure, it's needed
}
// If uncomment next 2 lines, then problem moves here from end of using block
//proc.Close();
//Logger.Debug("1. after .Close call"); // <------------------ This log we don't see sometimes
Logger.Debug("2. last inside using-block");
} // end of using block
Logger.Debug("3. after using-block"); // <------------------ This log we don't see sometimes (if `.Close` wasn't called before)
return 0;
}

I've figured out with my issue. The problem was: my process once in a while** spawned child daemon-process, which is ment to work forever. Child process (by design) always inherits redirected output streams if parent streams are redirected.
From the other side, .Kill() don't kill child processes - only for which it was kalled. (Until .Net 5.0, where .Kill(bool) solves this problem).
So .Dispose() or .Close() never finish, 'cos waits to release output streams, which are held by infinite child process.
It was very interesting adventure to figure out, what is happening =)
**) - that's why reproduce was very unstable
PS: Thank to #Blindy for directing me the way quite close to where the real cause of problem lie.

Related

Stopping robocopy from C# [duplicate]

If process.Kill() is called from another thread or even another program, the process never comes out of WaitForExit() if the batch script used robocopy.exe until it is finished as if it wasn't killed.
Robocopy.exe is called from the batch script. Every other script or program ends as you'd expect.
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = "batch.bat";
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
startInfo.RedirectStandardOutput = true;
startInfo.OutputDataReceived += CaptureHandler;
startInfo.RedirectStandardError = true;
startInfo.ErrorDataReceived += CaptureHandler;
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
The batch script looks like:
#echo off
call "robocopy.exe" "somedir" "somedest" /mir /fp /ndl /njh /njs /ns
I have a feeling it has to do with the output handlers.
I tried using process.CancelErrorRead and process.CancelOutputRead() as well after the Kill() call and before, no luck.
Oddly, if you use process.WaitForExit(timeout) overload, it will return true immediately after Kill() from the other thread. However, it's lying. The process is still running! If you try process.WaitForExit() again, as per the MSDN doc, it will still wait for the process to finish despite HasExited saying true.
To ensure that asynchronous event handling has been completed, call the WaitForExit() overload that takes no parameter after receiving a true from this overload.
https://msdn.microsoft.com/en-us/library/ty0d8k56(v=vs.110).aspx
You are successfully killing the batch processor (cmd.exe) but doing so won't kill robocopy, which is a separate process.
It doesn't seem to be documented, but when we look at the .NET source code it turns out that the Process.WaitForExit() method doesn't just wait for the process to exit, it also waits for end-of-file on the standard output and standard error streams. In this scenario, that means that it waits for robocopy to finish even after the batch processor has been killed.
(The overload of Process.WaitForExit with a timeout does not have this extra logic.)
I think this constitutes a bug in the .NET framework. At the very least, it should be documented.
As a workaround, you can use .HasExited and/or the version of WaitForExit with a timeout to determine whether the process has exited or not. Of course, in your scenario you might prefer to wait for grandchild processes, in which case your code is already behaving as desired.
I ran into the same problem. In my case, dropping the /mt switch from the RoboCopy argument list seemed to fix the issue.
Having followed up on Harry Johnston's helpful answer, I found that the process completes normally when you avoid RedirectStandardOutput = true. If this isn't an acceptable solution I found that using robocopy's /LOG:"C:\logs\robocopy.txt" switch to send its standard output to an external log file also works (although you lose the ability to get the file/directory log output from the process object itself).
Looks like right now the only way to do this without the application knowing to terminate Robocopy.exe specifically is to do kill the children of the script process before killing the script itself:
Kill process tree programmatically in C#
/// <summary>
/// Kill a process, and all of its children, grandchildren, etc.
/// </summary>
/// <param name="pid">Process ID.</param>
private static void KillProcessAndChildren(int pid)
{
ManagementObjectSearcher searcher = new ManagementObjectSearcher
("Select * From Win32_Process Where ParentProcessID=" + pid);
ManagementObjectCollection moc = searcher.Get();
foreach (ManagementObject mo in moc)
{
KillProcessAndChildren(Convert.ToInt32(mo["ProcessID"]));
}
try
{
Process proc = Process.GetProcessById(pid);
proc.Kill();
}
catch (ArgumentException)
{
// Process already exited.
}
}

Reading StdOut after process has been abnormally terminated using Process.kill

i am invoking an exe via c# Diagnostics.Process class and read output from it's StdOut. The process is forcefully terminated in case it doesn't automatically terminates in a specified time, something like:
process.StartInfo.FileName = #"D:\t.exe";
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardInput = false;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = false;
process.WaitForExit(500);
if (!process.HasExited)
{
process.Kill();
process.WaitForExit();
}
string stdOutContents = process.StandardOutput.ReadToEnd();
Now the problem is the code works succesfully when the exe terminates normally. But in case it fails to terminate normally (usually the exe gets stuck in an infinite loop somewhere), stdOutContents is read as an empty string.
How can i read StdOut after the process is killed (without using process.OutputDataReceived event technique)? (It has been verified that the exe-in-question does always writes something onto StdOut even if it gets stuck somewhere).
Update 1
Details about Exe which is being invoked (refereed as 'native app' across this question)
It is a small utility implemented in c language and compiled using MS C++ compiler. It does its job while simultaneously outputting status information onto the StdOut (using putchar).
There are only two possible cases of operation:
It will run successfully while simultaneously printing some data onto the StdOut.
It will run normally to a certain point (simultaneously outputting data on StdOut) and then get stuck in an infinite loop. (This is an acceptable behavior).
Both scenarios have been verified using cmd.
Details about new attempts
i wrote a c# app (referred as dummy app) which mimics the native app behavior and this code works fine. However when run for the native app, i get nothing at all.
i don't understand why the code cant read the contents outputted by the native app?
i also tried using event handler for OutputDataReceived. It gets called only once with args.Data = null when the code tries to kill the process. Inspecting the behavior for dummy app revealed that when process.kill is called, the handler is invoked with args.Data = null. So this seems to be a standard behavior of sorts for both apps.
i also tried changing the newline characters for native app. Since it is implemented in c language, it uses \n for newline. i tried using both \r\n pair for newline but StdOut is still blank (for case 2).
I had the same interrogation and the doc of Process.Kill says
Data edited by the process or resources allocated to the process can be lost if you call Kill.
Which seems to indicate that you cannot rely on reading the StandardOutput of a process, although it is not clearly stated that the output / error streams are disposed.
I finally got inspired by this answer
How to spawn a process and capture its STDOUT in .NET?
and I use the following code :
var info = new ProcessStartInfo("some.exe");
info.CreateNoWindow = true;
info.RedirectStandardOutput = true;
info.UseShellExecute = false;
using (var p = new Process())
{
p.StartInfo = info;
var output = new StringBuilder();
p.OutputDataReceived += (sender, eventArgs) =>
{
output.AppendLine(eventArgs.Data);
};
p.Start();
p.BeginOutputReadLine();
if (!p.WaitForExit(5000))
{
Console.WriteLine("Taking too long...");
p.Kill();
Console.WriteLine("Process killed, output :\n" + output);
}
}
Same pattern can be used with the ErrorDataReceived
Note that one could miss some unflushed output from the child process, however in my case I don't expect much from a process that requires to be killed, at most some information for debugging purposes.

C# equivalent to fork()/exec()

I'm developing a program that needs to call an outside program, but needs to wait for it to execute. This is being done in C# (to which I am brand new, but have lots of experience in C++, Qt, and C) and CreateProcess does not seem to be what I'm looking for (starts the process, then forgets it, which I don't need).
This is one of my first Windows projects (or at least, only Windows and definitely only .NET) and I'm much more used to doing this sort of thing for *nix where I would use fork and then exec in the child, then wait for the child to terminate. But I have no idea where to even start looking to do something like this.
Oh, and I'm pretty sure I'm stuck in .NET because I need read access to the registry to complete this project and .NET's registry access is absolutely amazing (in my opinion, I don't have anything to compare it to).
Thanks.
You can use the Process class. It lets you specify some options about how you want to execute it, and also provides a method which waits the process to exit before executing the next statement.
look at this link (the msdn reference):
http://msdn.microsoft.com/fr-fr/library/system.diagnostics.process.aspx
basically what you can do is:
Process p;
// some code to initialize it, like p = startProcessWithoutOutput(path, args, true);
p.WaitForExit();
an example of initializing the process (that's just some code I used once somewhere):
private Process startProcessWithOutput(string command, string args, bool showWindow)
{
Process p = new Process();
p.StartInfo = new ProcessStartInfo(command, args);
p.StartInfo.RedirectStandardOutput = false;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = !showWindow;
p.ErrorDataReceived += (s, a) => addLogLine(a.Data);
p.Start();
p.BeginErrorReadLine();
return p;
}
as you can see in this code you can also do some output redirection, error redirection.... If you dig in the class I think you'll find quite quickly what you need.
var p = System.Diagnostics.Process.Start("notepad");
p.WaitForExit();
You can use the Process class to start external processes.
It will let you start arbitrary programs
http://msdn.microsoft.com/en-us/library/system.diagnostics.process.aspx

ReadToEnd from std output of process and waitforexit

From the MSDN example of using stdoutput of newly created process:
// This is the code for the base process
Process myProcess = new Process();
// Start a new instance of this program but specify the 'spawned' version.
ProcessStartInfo myProcessStartInfo = new ProcessStartInfo(args[0], "spawn");
myProcessStartInfo.UseShellExecute = false;
myProcessStartInfo.RedirectStandardOutput = true;
myProcess.StartInfo = myProcessStartInfo;
myProcess.Start();
StreamReader myStreamReader = myProcess.StandardOutput;
// Read the standard output of the spawned process.
string myString = myStreamReader.ReadLine();
Console.WriteLine(myString);
myProcess.WaitForExit();
myProcess.Close();
If instead of myStreamReader.ReadLine() I'm using myStreamReader.ReadToEnd() shall I still use myProcess.WaitForExit()?
Or ReadToEnd() will wait until the process is finished?
EDIT:
Sorry for the diversion, to directly answer your question. Yes, you need to call Process.WaitForExit();. This will ensure that the process has yielded all its output before you call ReadToEnd()
ReadToEnd is synchronous function. Hence if you don't call it in your code, it will block your main thread until it captures only the first output from the StandardOutput, then that's it. But using WaitForExit will ensure that you have everything.
Also you might consider doing an asynchronous read of the process's output, see this MSDN Example that implements OutputDataRecieved
"ReadToEnd" is a function stored in "StreamReader" object and I don't think it has something to do with waiting for a process to exit, however the "Process" class might handle that itself. By the way, all the abilities "StreamReader" has are not useful in the situation you mentioned.
In my point of view, "WaitForExit" should be called and as you did "Close" too. Because they will release some system resources that no method else can. As far as I know, "ReadToEnd" method has nothing to do with calling those two.
Cheers

Reusing Process object and IO redirects in C#

I'm using the data ready events of the Process class to get information from the standard output and standard error of a running process.
It works great on the first run, but after calling Stop() then Start() to force a restart of the application, I no longer recieve data. I've tried CancelErrorRead() but no luck there.
I'm considering just re-instantiating the object every time I need to re-run the app, but it seems silly to need to do that.
Any advice on how to re-use a Process object to restart a stopped process?
Relevant code chunks:
Constructor:
ProcessStartInfo objStartInfo = new ProcessStartInfo();
objStartInfo.CreateNoWindow = true;
objStartInfo.RedirectStandardInput = true;
objStartInfo.RedirectStandardOutput = true;
objStartInfo.RedirectStandardError = true;
objStartInfo.UseShellExecute = false;
objClient = new Process();
objClient.StartInfo = objStartInfo;
objClient.EnableRaisingEvents = true;
objClient.OutputDataReceived += new DataReceivedEventHandler(read);
objClient.ErrorDataReceived += new DataReceivedEventHandler(error);
Start:
objClient.StartInfo.FileName = strAppPath;
objClient.StartInfo.Arguments = strArgs;
start();
objClient.BeginErrorReadLine();
objClient.BeginOutputReadLine();
Stop:
objClient.Close();
objClient.CancelErrorRead();
objClient.CancelOutputRead();
Your Process object is not associated with a process until you call Start() (or use one of the static methods off Process). A stopped/closed process is functionally the same as no process at all. Given that, it's hard to believe there's any overhead to creating a new Process object, when compared to the (relatively enormous) cost of creating processes on Windows. Just create new Process objects as needed.
According to msdn you should call BeginOutputReadLine and BeginErrorReadLine to enable asynchronous reads from StandardOutput or StandardError using events.
Have a look at the remarks section on
BeginOutputReadLine

Categories

Resources