views:

536

answers:

4

C# .NET 3.5

I have a console application that is being called by another application on the computer. This console app runs continuously, and listens for data on stdin from the "parent" process.

However, when the parent is stopped or killed, the console app that it started continues. Under normal circumstances, it sits and idles waiting for input from stdin, using minimal resources. However, as soon as the parent goes away, this console app spikes the CPU and starves the core it's running on with near 100% utilization. This continues until I manually kill the process.

Ideally, the calling parent would clean up after itself, particularly since this is happening under normal (non exceptional) "stop" conditions. Unfortunately, this parent process is out of my hands.

My first thought was to grab the invoking parent from within the console app, and monitor its PID. If the parent process goes away, I the console app would terminate itself. Currently, I'm doing this by:

Process process = Process.GetCurrentProcess();
m_ParentPID = 0;
using (ManagementObject mgmtObj = new ManagementObject("win32_process.handle='" +     process.Id.ToString() + "'"))
{
    mgmtObj.Get();
    m_ParentPID = Convert.ToInt32(mgmtObj["ParentProcessId"]);
}
string parentProcessName = Process.GetProcessById(m_ParentPID).ProcessName;
Log("Parent Process: " + parentProcessName + Environment.NewLine);

// Create a timer for monitoring self.
Timer timer = new Timer(new TimerCallback(sender =>
{
    if (m_ParentPID != 0)
    {
        Process parent = System.Diagnostics.Process.GetProcessById(m_ParentPID);
        if (parent == null)
        {
            Log("Parent process stopped/killed.  Terminating self.");
            System.Environment.Exit(0);
        }
    }
}));

// Kick on the timer
timer.Change(m_ExitWatcherFrequency, m_ExitWatcherFrequency);

This only partly works though - it stops the CPU spike, but if I look at my processes from Sysinternals wonderful process monitor, I can see DW20.exe running - the "Microsoft Application Error Reporting" program. And it just ... sits there, and the console app remains in memory.

What should I be doing here to correctly terminate the process to avoid this continuous CPU spike and unreleased memory? Eventually this needs to be running without intervention.

P.S. I am using a command line application here as a "long running program" instead of a Windows Service or web service because the parent program can only be configured to execute a command line app for which it passes data in via stdin. (for those who are curious, this is ejabberd, using external authentication).

EDIT:

The code that waits for input from stdin is:

// Read data from stdin
char[] charray = new char[maxbuflen];
read = Console.In.Read(charray, 0, 2);

I mention before that when the parent terminates, the console app goes crazy on the CPU. I attached a debugger to it from Visual Studio, and it is, in fact, still sitting on that line Console.In.Read. In theory then, when the self-monitoring timer triggers and sees the parent is gone, it attempts an System.Environment.Exit(0) when the other thread is on that Read() line.

+3  A: 

To watch for a process exit, use the Process class, and subscribe to the Exited event.

Edit: Removed interop comment.

Edit (in response to comments):

Most times, what is done in a situation like this, is pass some data back in, so you can stop blocking on the Console.In.Read method call.

So, suppose you set a flag, IsDone, to true when you discover the parent process is done. Now, because the process you're waiting for wont send anything anymore, you still need to receive something on standard input, or you'll block forever. So, in your event handler/timer code, write something to the standard input of your own process (it can even be a special value to signal you're done if you want). This will get you past the Console.In.Read method. Once out, check to see if the IsDone flag is set -- if it is, stop processing and return from your main method -- No System.Environment.Exit required.

Nader Shirazie
But what do I do to have a program terminate itself when it sees that other process has exited? Also, I don't have access to the parent program, so I can't modify it to have it send any signals to the child.
Matt
how exactly does your console app wait for input? some kind of loop? can you post the code (or pseudocode)? that'll be useful to figure out what hte best approach is.System.Environment.Exit(0) should work -- so I'm curious as to what's causing the problem.
Nader Shirazie
Updated question with some additional information per your question, Nader
Matt
+1 for the IsDone idea. I tried that first, before I saw devstuff's answer, and it worked great for me. But ended up going with devstuff's answer in the end because it was cleaner and directly looked at stdin state instead of the round-about way of looking at the parent like I was trying to do. Thanks!
Matt
+2  A: 

I'd solve this by adding a thread to your console app that basically monitors the presence of the calling app in the process table. This is all off the top of my head, but here's some pseudocode:

using System.Thread;
using System.Diagnostics;

main(){
     Thread monitoringThread = new Thread(new ThreadStart(monitor));
     monitoringThread.Name = "Monitor";
     monitoringThread.Start();
}

and in the function monitor:

void monitor(){
     Process[] theCallers = Process.GetProcessesByName("CallingProcessName");
     theCallers[0].WaitForExit();
     Process.GetCurrentProcess().Kill();
}

Assuming you only have one calling process in the process list, as soon as that process ends, this one will as well. You can also put much nicer cleanup code for your own process, rather than having it just end abruptly.

mmr
Isn't that essentially what I did with my Timer() above? (as far as monitoring goes). How does Process.GetCurrentProcess().Kill() differ from System.Environment.Exit(0) ?
Matt
This solution doesn't have a watching frequency, but simply gets an event when the process dies and then proceeds with the rest of the thread. I used Kill() instead of Exit(0) just in my own code, because kill works right now while exit goes through finalizers and such. If you don't care about finalizers, then kill will be fine, but if you want to clean up, then exit(0) is probably better. However, it may be your finalizers that are causing your processing spike, I'm not sure.
mmr
Ahh, I see what you mean. So you just have another thread here that's waiting for the exit, instead of polling for a non existent process.
Matt
exactly. The polling might also be causing the spike, for all I know.
mmr
+5  A: 

It sounds like your process is going into a hard loop due to the console input stream being closed when the parent exits. Are you checking the return value from Console.In.Read? It will return zero when the stream has been closed. At that point break out of the loop and let your Main() method exit by itself.

And if you're running multiple threads they'll have to be finished first, using Thread.Join or equivalent.

devstuff
+1 explaining the cpu spike
Nader Shirazie
Hm, good points devstuff. I hadn't really stopped to think about why the CPU spikes, but hitting a continuous loop because Console.In.Read always returns zero makes a lot of sense.
Matt
only caveat here is that if you ever get 0 bytes returned during normal operation, you will terminate the console app incorrectly.
Nader Shirazie
@nader: That can only occur if the parent process closes their output stream, so its a pretty good bet that this is an exit indicator. :-)
devstuff
@devstuff: Makes sense. I'm obviously unfamiliar with the way Read works :)
Nader Shirazie
A: 

I like mmr's idea of watching the parent process from the child. If you have a way to pass the parent's PID on the command line, that would be even better. From inside the parent process, you can get the PID:

System.Diagnostics.Process.GetCurrentProcess().Id
280Z28
can't modify the parent
Nader Shirazie