views:

440

answers:

6

My project has an object that creates a process. It this object's Dispose function, it kills the process (or tries to). However, if the program crashes, it leaves the process running and doesn't clean up. Which causes the program to fail next time because it tries to launch the process again and can't get a lock on it.

How can I make sure this process is killed? I always use the object that creates the process in a using block

For reference I'm using C# and .NET 3.5

A: 

What kind of program is being launched? It should be possible to launch a second instance even if the first instance is still running - launching the process doesn't lock anything (unless the program itself does something weird).

And how is the object launching the process? If it has a handle to it, it should be able to terminate it instantly by calling the TerminateProcess API.

Here's a really hacky solution to the problem of ensuring a spawned process will die if the caller dies: call DebugActiveProcess, passing the child process ID.

Daniel Earwicker
A: 
Foo bar = new Foo();
try
{
  bar.DoSomething();
}
catch // if required
{
}
finally
{
  if(bar != null)
  {
    bar.Dispose(); // code from finally clause is ~always executed
  }
}

which is equal to

using(Foo bar = new Foo())
{
  // ..
}
abatishchev
Ignoring unitialized variable, that is pretty much the same as "using" (see original question). And no, that isn't "always executed" - there are a range of things that are often too terminal - thread-abort, stack-overflow, OOM, process-kill, "pull the plug out the wall", etc.
Marc Gravell
Hello Marc. Yes, I known, and you're absolutely right, not always. But in the most cases of normal application life cycle, I think. Nothing could helps from plug out of the wall :) My code could from application termination as result of an exception
abatishchev
But my point is: the OP states they are using a "using" block. Which means the above code is unnecessary... you've just used more key-strokes to achieve the same thing.
Marc Gravell
Thanks! Indeed, I have read that 'using' equals to try { } finally { Dispose(); } but forgot about that! You're right as always, I believe :)
abatishchev
+3  A: 

Define "crashes"; there are different levels of crash... for example, if something actively kills your process, you will have very little chance to run any Dispose/finalizers etc - but if your thread unwinds gracefully (even through exception), you should be OK (since you are using using). Can you clarify what the setup is?

Marc Gravell
It doesn't take care of it when it throws an exception and I have to kill it from VS, which is the problem.
Malfist
Well, how does your code attempt to exit?
Marc Gravell
There's a try catch finally wrapped around the Application object, but it's used to close the log file, the object that creates the process is used inside the application. The only time the object that creates the process is used is inside a using block, and the Disposable() kills it or should
Malfist
The interesting question is: does the call stack ever get there. To be honest, it is going to be hard to trace without a better understanding of when (in the app life-cycle) this object is created and (supposedly) killed...
Marc Gravell
I'm tempted to delete this answer, since it isn't actively contributing much - but I think you need to add a bit more detail to get a useful answer, sorry.
Marc Gravell
A: 

What you really want is bi-directional process monitoring so that if either the parent process or the child process dies, you can take appropriate action. The simplest way to do this is to have them ping each other on a timer (every 30 seconds, e.g., you have to tune for your needs) and if no response comes by the time you send the next ping then you can assume the other process has died, locked up, or otherwise stopped playing nice.

If the parent stops responding the child should just exit. If the child stops responding, kill it and start a new one.

How to implement the pinging is left as an exercise for the reader, but any IPC mechanism will do--window messages, RPC calls, pipes... whatever strikes your fancy. Keep it simple; I'm sure .net has something you can use.

jeffamaphone
+1  A: 

I know this will get me yelled at, but how about Enviroment.Exit(). That should kill everything, every time =) It is still better to do the above options though.

Also:

In your Program.cs, before you call Application.Run(Form), do this:

AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

and then put in a handler that is something like this:

 static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
 {
  try
  {
   String CurrentThreadName = System.Threading.Thread.CurrentThread.Name;

   if (e.IsTerminating)
    Logger.Log("Program", 0, String.Format("Received fatal unhandled exception on '{0}' thread.  Exception will follow.", CurrentThreadName), MsgCategory.Critical, 50);
   else
    Logger.Log("Program", 0, String.Format("Received unhandled exception on '{0}' thread.  Exception will follow.", CurrentThreadName), MsgCategory.Error, 25);

   if (e.ExceptionObject != null)
    Logger.LogException("Program", String.Format("Unhandled System Exception on '{0}' thread.", CurrentThreadName), 50, (Exception)e.ExceptionObject);
  }
  catch
  {
  }
  if (e.IsTerminating)
  {
   Exception ThisException = (Exception)e.ExceptionObject;
   String CurrentThreadName = System.Threading.Thread.CurrentThread.Name;
   MessageBox.Show(
     String.Format(Localization.GetString("fatal_error"), ThisException.GetType().ToString(), ThisException.Message, ThisException.StackTrace, CurrentThreadName)
     , Localization.GetString("dialogtitle")
     , MessageBoxButtons.OK
     , MessageBoxIcon.Error
     , MessageBoxDefaultButton.Button1);
  }
 }
JasonRShaver
Processes can crash sufficiently badly that no exit code will ever get executed. Throwing up a message box like that isn't necessarily the best option. It requires user interaction and prevents an automatic restart. I'd do it in debug builds, primarily, and raise an alarm to operators in release.
Pontus Gagge
Aye, the messagebox is to provide something that a customer (or QA agent most commonly) can relay to the development team. It could also be a spot to do a log dump or something else. For my high-uptime product, it has worked out quite nicely. =)
JasonRShaver
+1  A: 

If you want to ensure child process termination, consider creating a separate and simpler watchdog process, watching the process space for termination of either one. It's usually easier to make it bulletproof. There's no way to guarantee the execution of cleanup code in all circumstances.

However, you don't really provide enough context on how your child process is created to permit any relevant advice on how to design the watchdog to be aware of the parent and child processes.

Pontus Gagge
It's just a process that's started with Process.Start().
Malfist
How would I do the watch dog?
Malfist
You need to have some way for a watchdog to identify the parent and child processes, whether by name or by registering them (more complex). See e.g. http://www.codeproject.com/KB/security/WatchDog.aspx for a more ordinary watchdog service (for keeping alive, not killing processes!).
Pontus Gagge