Introduction
This article is about running modules in independent child processes to isolate them from the main application. Server applications make the use of external modules in order to extend functionality. The classic implementation to this case is to define base interfaces and use factory method classes to load and use the modules from class DLLs. The problem starts when the number of modules increases. Since they run in the context of the server application, the application becomes unsafe regarding unexpected failure in any of the extensions. In other words, crash in one of the extension modules will kill the main application. Multi-Process Architecture means that each driver runs in its own child process. This approach separates the modules from the main server application and also each module from the other.
Background
There are few levels of module isolation.
The simplest way is using threading models. Each module runs in its own thread, while threading synchronization methods are used to access common resources. Threading makes the modules run concurrently but not so isolated.
.NET offers another way of isolation called AppDomain. Practically, it is different from threads in that it can be unloaded when misbehavior is detected. The drawback of AppDomain is that it stops the application when unhandled exception occurs. Using child process is the only way to protect the server application from unexpected behavior and failures in the modules.
The Multi-Process Architecture has been used lately by internet browsers that use tabs, like Internet Explorer 8 and Google Chrome. The display rendering functionality is isolated by using child process for each tab. It protects the browser application from misbehavior. Read more in Google Chrome Multi-process Architecture.
Disadvantage
The main problems of using multi-process architecture are:
- Each module consumes resources of process. It means more memory and maybe some other issues that should be considered.
- Duplex inter-processing communication should be added to the module interface.
The Server
On the server side, the code uses System.Diagnostics.Process
and System.IO.Pipe.NamedPipeServerStream
classes to manage the child process life time.
In the demo, thread is started to begin communicating with the child process.
public bool Start(string paramUID)
{
m_PipeID = paramUID;
m_PipeMessagingThread = new Thread(new ThreadStart(StartIPCServer));
m_PipeMessagingThread.Name = this.GetType().Name + ".PipeMessagingThread";
m_PipeMessagingThread.IsBackground = true;
m_PipeMessagingThread.Start();
ProcessStartInfo processInfo = new ProcessStartInfo
("MultiProcessIPCClient", this.m_PipeID);
m_ChildProcess = Process.Start(processInfo);
return true;
}
void StartIPCServer()
{
if (m_PipeServerStream == null)
{
m_PipeServerStream = new NamedPipeServerStream(m_PipeID,
PipeDirection.InOut,
1,
PipeTransmissionMode.Byte,
PipeOptions.Asynchronous,
BUFFER_SIZE,
BUFFER_SIZE);
}
try
{
m_PipeServerStream.WaitForConnection();
Console.WriteLine(string.Format("Child process {0} is connected.", m_PipeID));
}
catch (ObjectDisposedException exDisposed)
{
Console.WriteLine(string.Format("StartIPCServer for process {0} error:
{1}", this.m_PipeID, exDisposed.Message));
}
catch (IOException exIO)
{
Console.WriteLine(string.Format("StartIPCServer for process {0} error:
{1}", this.m_PipeID, exIO.Message));
}
bool retRead = true; ;
while (retRead && !m_IsDisposing)
{
retRead = StartAsyncReceive();
Thread.Sleep(30);
}
}
The Child Process
The code example demonstrates how to create a windowless child process and how to communicate with the parent using System.IO.Pipe.NamedPipeClientStream
.
The first step is to create a Windows Form application and then use the Run
method with the ApplicationContext
class as shown below:
static class Program
{
[STAThread]
static void Main(string[] args)
{
Application.Run(new AppContext(args[0]));
}
}
The AppContext
implements ApplicationContext
class that basically runs the message loop the same way as Windows.Form
does. In this example, timer is started to send periodic 'Keep Alive' message to the parent application.
class AppContext : ApplicationContext
{
private string m_ID;
private NamedPipeClientStream m_PipeClientStream;
System.Timers.Timer m_KeepAliveTimer = new System.Timers.Timer(2000);
public AppContext(string paramID)
{
Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
m_ID = paramID;
StartIPC();
m_KeepAliveTimer.Elapsed +=
new System.Timers.ElapsedEventHandler(m_KeepAliveTimer_Elapsed);
m_KeepAliveTimer.Interval = 2000;
m_KeepAliveTimer.Start();
}
void m_KeepAliveTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
...
}
void Application_ApplicationExit(object sender, EventArgs e)
{
System.Diagnostics.Trace.WriteLine("Application Exit " + m_ID);
}
void StartIPC()
{
System.Diagnostics.Trace.WriteLine("Starting IPC client for " + m_ID);
m_PipeClientStream = new NamedPipeClientStream(".",
m_ID,
PipeDirection.InOut,
PipeOptions.Asynchronous);
try
{
m_PipeClientStream.Connect(3000);
System.Diagnostics.Trace.WriteLine("Connected to IPC server. " + m_ID);
}
catch(Exception ex)
{
System.Diagnostics.Trace.WriteLine("StartIPC Error for " + ex.ToString());
return;
}
}
}
Inter-Process Communication (IPC)
The IPC method that I used is NamedPipe
. In C#, we can use System.IO.Pipes
from version .NET 3.5. In earlier frameworks, we must use wrappers for NamedPipe
.
NamedPipe
is a light weight approach for IPC. Using WCF, Remoting or simple networking is also acceptable.
Summary
The code example is just a simple demo that advices how to robust server and other applications that can't rely on third party implementations. The way Windows manages processes makes the modules run independently from the main application.
Moving to multi-process means doing some work, we must add IPC protocol instead of the existing simple interface. After I did this work in a large and busy server application, I must say that it was worth the effort, no more applications crash or hang.
History
- 31st May, 2009: Initial version