You can find source code on GitHub.
Introduction
First of all you should consider do you really need to make your application or process unkillable/unterminatable. Fairly in most cases this is nothing but a bad design that users will want to kill you for.
If you have such a requirement it is better to dig into the details, find out what real life case it is covering and implement it using best practices.
Why is it so? OS provides you rich API so you can really do sort of unkillable process, but sometimes it is not a supported scenario and eventually your application will be full of bugs and even blocked by antivirus software. Native scenarios may include:
- Windows service
- Security permissions
- Debugger API
- Other
Other scenarios:
- process naming tricks
- keep alive processes (the one article is about)
- User-mode hooks with loaded DLLs or injected threads
- Kernel-mode call hooks
- Direct kernel object manipulation (DKOM) This one really nice, but looks like real security breach, that's why almost all anti-virus programs find such apps infected. You can find description, sources and executable here.
- Usage of debugger API.
- Tool manipulation, when you target specific tools with 'patches' to protect you processes.
You can find more info about these approaches here.
In this article I'm describing keep alive processes solution. I suggest not using it in real life projects as it may bring bugs, unpredictable behavior and instability. But of course you can handle these things by yourself, please see section "Possible Improvements".
Functionality
This solution consists of:
- main (client) process that will be restarted by helper (keep alive) process in case it exited
- helper (keep alive) process that is restarted in case of termination
Client process is a single instance application and helper process - multi-instance.
User starts Client application, terminates it, then terminates KeepAlive process then exits the application
First of all Keep Alive process has functionality to run Client application or itself and then exit. This functionality is needed to prevent user from terminating process tree. When you start a process which starts another process, you're able to terminate them almost simultaneously for example by clicking parent process in Task Manager and choosing 'End Process Tree'. But when you break parent child relationship having process in the middle which exits right after starting another process, you're preventing user from terminating process tree. So when I write about process starts other process I mean starting it with help of intermediate process which breaks parent-child relationship.
When user starts Client application if it is not running it will run KeepAlive process. Keep alive process receives process id and waits until the process with provided id will exit. When it exists, Keep Alive process immediately restarts it. The same is for Client process it waits in separate thread for Keep Alive process exit. When Keep Alive process exits, client application starts it again.
To exit you can define some flag that will break the restarting loop and set it upon your exit event. Also you may want to protect user from frequently restarting processes in case of unstable code that crashes application on start. In this case application will crash and immediately restart and we're having a loop here. To handle this KeepAlive process checks if it has restarted Client app more than 10 times during last 10 minutes, if it is so Keep Alive process stops restarting and exits.
This solution allows you to:
- immediately restart your application terminated by user with Task Manager (end process)
- immediately restart your application if user killed a process tree of your applications (end process tree)
- have both keep alive and client processes restartable
Drawbacks of the solution:
- it is not reliable, you can kill processes if you write an app that will kill given processes near simultaneously (I didn't tried it, but it should be so) or delete an executable when it's being restarted, but still applicable for some tasks (see possible usage)
- cold start on slow machines - gives some time to user to terminate client process
- it may be unstable during development, small bug can lead to restarting loops and other funny things
How to actually close them while testing.
- Type exit in console window, press Enter.
- Use SimultaneousTerminate tool that is also in a package
- As this approach doesn't do anything with threads suspension, you can suspend processes "KeepAlive" and "KeepAliveClient" then kill them (for example, with SysInternals ProcessExplorer)
- Try to delete executables while keep killing processes with Task Manager / Process Explorer
- Write an application that will try to kill chosen processes nearly simultaneously
Possible usage and improvements
Usage:
- You want your process to be restarted when it failed and terminated. So we are dealing here with unstable application that we want to restart when it fails.
- You want to prevent users from terminating it with Task Manager. Task Manager doesn't terminate both of them without letting new one to be created.
Possible improvements:
- You can check if process is suspended and resume it or start another one.
- Error handling. There're a lot of places where you can add null checks or try/catch blocks as long as state of objects may be changed very fast, so it's better to add proper exception handling.
- Keep alive doesn't affect OS shutdown, but to quit gracefully when OS is shutting down you may want detect it with system events and bypass keep alive code
- Rewrite helper (keep alive) process in native code :) It is very small and simple, so with this we can improve its performance a little bit.
- Many more..
Using the code
Solution consists of three projects: Client application (which you want to make auto restartable), KeepAlive application which has functionality to break parent-child process relationship and keeps Client alive and Simultaneous Terminate, which just terminates processes in a manner that doesn't allow then to restart.
To test functionality you can run KeepAliveClient, try to terminate it or KeepAlive process with Task Manager. For exit type 'exit' and press Enter.
Client application code
This is single instance application, so Mutex is used to guarantee this. Application accepts parameters to find KeepAlive process that launched Client application. If Client application is launched by user nothing is provided so _processId
will be 0.
After parsing parameters if there're some, KeepingAlive
method is launched in separate thread. This method does the following:
- Starts KeepAlive process
- Waits for KeepAlive process exit, to restart it
- Uses intermediate process to run KeepAlive process (in order to break parent-child process relationship)
- Breaks the restarting loop if flag
_exiting
is set up
public class Program
{
private const string KeepAlive = "KeepAlive.exe";
private static Process _keepAliveProcess;
private static Mutex _instanceMutex;
private static bool _exiting;
private static int _processId;
[STAThread]
public static void Main(string[] args)
{
if (!SingleInstance())
return;
if (args.Length > 1 && String.Equals(args[0], "keepaliveid"))
Int32.TryParse(args[1], out _processId);
var thread = new Thread(KeepingAlive);
thread.Start();
while (true)
{
if (Console.ReadLine() != "exit")
continue;
_exiting = true;
ReleaseSingleInstance();
_keepAliveProcess.Kill();
Environment.Exit(0);
}
}
private static void KeepingAlive()
{
while (true)
{
if (_exiting)
return;
if (_processId == 0)
{
var kamikazeProcess = Process.Start(KeepAlive,
string.Concat("launchselfandexit ", Process.GetCurrentProcess().Id));
if (kamikazeProcess == null)
return;
kamikazeProcess.WaitForExit();
_keepAliveProcess = Process.GetProcessById(kamikazeProcess.ExitCode);
}
else
{
_keepAliveProcess = Process.GetProcessById(_processId);
_processId = 0;
}
_keepAliveProcess.WaitForExit();
}
}
private static bool SingleInstance()
{
bool createdNew;
_instanceMutex = new Mutex(true, @"Local\4A31488B-F86F-4970-AF38-B45761F9F060", out createdNew);
if (createdNew) return true;
Debug.WriteLine("Application already launched. Shutting down.");
_instanceMutex = null;
return false;
}
private static void ReleaseSingleInstance()
{
if (_instanceMutex == null)
return;
_instanceMutex.ReleaseMutex();
_instanceMutex.Close();
_instanceMutex = null;
}
}
KeepAlive application code
KeepAlive has functionality to launch client application or self and exit. This is basically what allows us to break parent-child process relationship.
When Client launches KeepAlive it provides its process id with parameters, so KeepAlive knows which process exit it should wait for. In case there's no client running (it exited/terminated) KeepAlive will launch it.
Also there's a functionality that prevents frequent restarts. If application exited/was terminated 10 times within 10 minutes it will no longer be launched.
public class Program
{
private const string KeepAlive = "KeepAlive.exe";
private const string KeepAliveClient = "KeepAliveClient.exe";
private static readonly SortedSet<DateTime> RestartHistory = new SortedSet<DateTime>();
[STAThread]
public static void Main(string[] args)
{
if (args.Length < 2)
return;
var action = args[0];
if (action.Equals("launchclientandexit", StringComparison.OrdinalIgnoreCase))
{
LaunchAndExit(KeepAliveClient, string.Concat("keepaliveid ", args[1]));
return;
}
if (action.Equals("launchselfandexit", StringComparison.OrdinalIgnoreCase))
{
LaunchAndExit(KeepAlive, string.Concat("clientid ", args[1]));
return;
}
if (action.Equals("clientid", StringComparison.OrdinalIgnoreCase))
{
var processId = 0;
Int32.TryParse(args[1], out processId);
if (processId > 0)
KeepingAlive(processId);
}
}
private static void LaunchAndExit(string fileName, string parameters)
{
var process = Process.Start(fileName, parameters);
if (process != null) Environment.Exit(process.Id);
}
private static void KeepingAlive(int processId)
{
while (true)
{
Process keepAliveProcess;
if (processId == 0)
{
var kamikazeProcess = Process.Start(KeepAlive,
string.Concat("launchclientandexit ", Process.GetCurrentProcess().Id));
if (kamikazeProcess == null)
return;
kamikazeProcess.WaitForExit();
keepAliveProcess = Process.GetProcessById(kamikazeProcess.ExitCode);
}
else
{
keepAliveProcess = Process.GetProcessById(processId);
processId = 0;
}
keepAliveProcess.WaitForExit();
var time = DateTime.Now;
RestartHistory.Add(time);
while (RestartHistory.Count > 0 &&
(RestartHistory.Min - time) > TimeSpan.FromMinutes(10))
RestartHistory.Remove(RestartHistory.Min);
if (RestartHistory.Count >= 10)
return;
}
}
}
History
- 10/23/2013 - Initial version.