JointCode.Shuttle, a fast, flexible and easy-to-use service-oriented framework for cross-AppDomain communication

0
17

Download JointCode.Shuttle and samples

Introduction

This article briefly describes a new technology to improve the functions and performance while marshaling across application domains.

Background

All .net/mono code runs within AppDomain boundry under the hood, yet AppDomain is not a common technology to use when developing these applications, this is because usually our code runs in one AppDomain (the default AppDomain), and we don’t create it ourselves, the runtime system creates it for us instead.

However, sometimes you just needs to create a secondary AppDomain and run code in it. For example, when you needs to load and unload assemblies at runtime without stopping the application.

In these cases, you needs to make cross-AppDomain calls. So, what’s the problem? Well, the problem is not that you make the calls, it’s how you make these calls.

Generally, most people uses the MarshalByrefObject subclassing based mechanism provided by the runtime to achieve the cross-AppDomain communication. This is the easiest and most convenient way to go, and everything will be fine, as long as the service to be operated across AppDomains inherits from MarshalByrefObject, like this: 

namespace JoitCode.Shuttle.SimpleSample
{
 public class MyService : MarshalByRefObject
 {
 public void Do() { }
 }

 class Program
 {
 static void Main(string[] args)
 {
 var serviceDomain = AppDomain.CreateDomain("ServiceDomain", null, null);
 
 var myService = (MyService)serviceDomain.CreateInstanceAndUnwrap
 (typeof(MyService).Assembly.FullName, 
 "JoitCode.Shuttle.SimpleSample.MyService");

 myService.Do();

 Console.Read();
 }
 }
}

Simple as it is, there are some limitations in using this approach.

  1. AppDomain is a single point accessing infrastructure, that is, a child AppDomain can only be accessed by its parent (or host) AppDomain that created it, and no other AppDomains, even the parent AppDomain of the host or sibling AppDomains created by the same host, can access it.
  2. Lack of flexibility, because the service must inherit from the MarshalByrefObject, this limits the flexibility.
  3. Performance concerns, tests show that cross-AppDomain method calls using this way is hundreds to a thousand times slower than normal method calls within a same AppDomain.
  4. Two-way communication, no two-way communications can be achieved this way.

In addition, there are Remoting, WCF, even message queuing, and other IPC mechanisms can be used to achieve cross-AppDomain communication as well, and these approaches eliminate more or less the above restrictions. However, we can imagine that the performance loss is bound to be more serious than MarshalByrefObject, and its learning costs will be higher, the implementation will be more complex.

So, it seems that there is no good solution. Is that right? 

Solution

The solution is simple, find another way! That’s why i created JointCode.Shuttle.

JointCode.Shuttle is a fast, flexible and easy-to-use service-oriented framework for cross-AppDomain communication. It’s aimed to be a replacement for MarshalByrefObject provided by the runtime libraries.

JointCode.Shuttle provides the same cross-AppDomain communication function as MarshalByrefObject, and features:

  1. Service-oriented.
  2. Access to any AppDomain from one AppDomain (the MarshalByrefObject only allow access to child AppDomains from the parent AppDomain).
  3. Better performance: 60 ~ 70 times faster than MarshalByrefObject.
  4. Services are manageable: dynamically register/unregister services at runtime without having to restart the application, or even restart AppDomain.
  5. Strong type, easy to use (while the MarshalByrefObject way relies on magic string to find the service type).
  6. Built-in IoC functionality for automatic service dependencies management.
  7. Supports for lazy type / assembly loading.
  8. The remote service lifetime can be managed by leasing, or on demand (the MarshalByrefObject way does not provide remote service life management).
  9. Simple and quick to get started.
  10. Support .net 2.0. 

How to use it

In the JointCode.Shuttle distribution package, it contains two files: JointCode.Shuttle.dll and JointCode.Shuttle.Library.dll, where JointCode.Shuttle.dll is a library written in managed language, and JointCode.Shuttle.Library.dll is a component written in non-managed language, which is depended by the former.

Prepare

To use JointCode.Shuttle, we first need to refer to JointCode.Shuttle.dll in our project, and copy the JointCode.Shuttle.Library.dll to the folder where JointCode.Shuttle.dll is compiled (for example, after the project is compiled, if the JointCode.Shuttle.dll is copied to the c:/projects/sampleproject folder, you need to manually copy the JointCode.Shuttle.Library.dll to this folder).

Using the code

As the JointCode.Shuttle is interface-oriented, so we first need to create a service interface (also called service contract), and apply ServiceInterface attribute to it.

[ServiceInterface]
public interface ISimpleService
{
 string GetOutput(string input);
}

Then we create a service class that implements the contract and apply the ServiceClass attribute to it.

[ServiceClass(typeof(ISimpleService), Lifetime = LifetimeEnum.Transient)]
public class SimpleService : ISimpleService
{
 public string GetOutput(string input)
 {
 return string.Format
 ("SimpleService.GetOutput says: now, we are running in AppDomain: {0}, and the input passed from the caller is: {1}",
 AppDomain.CurrentDomain.FriendlyName, input);
 }
}

Because we want to make cross-AppDomain calls, we need to write a class for starting/stopping the remote services and let it inherit from MarshalByRefObject.

public class ServiceProvider : MarshalByRefObject
{
 
 
 ShuttleDomain _shuttleDomain;

 public void RegisterServices()
 {
 
 var guid = Guid.NewGuid();
 _shuttleDomain.RegisterServiceGroup(ref guid,
 new ServiceTypePair(typeof(ISimpleService), typeof(SimpleService)));
 }

 public void CreateShuttleDomain()
 {
 
 _shuttleDomain = ShuttleDomainHelper.Create("domain1", "domain1");
 }

 public void DisposeShuttleDomain()
 {
 _shuttleDomain.Dispose();
 }
}

Now, we are ready to use JointCode.Shuttle:

namespace JoitCode.Shuttle.SimpleSample
{
 public static class ShuttleDomainHelper
 {
 public static ShuttleDomain Create(string assemblySymbol, string assemblyName)
 {
 return Create(assemblySymbol, assemblyName, null);
 }

 public static ShuttleDomain Create(string assemblySymbol, string assemblyName, ServiceContainer svContainer)
 {
 var dynAsmOptions = new DynamicAssemblyOptions
 {
 AccessMode = AssemblyBuilderAccess.Run,
 AssemblyName = new AssemblyName(assemblyName)
 };

 var options = new ShuttleDomainOptions
 {
 DynamicAssemblySymbol = assemblySymbol,
 DynamicAssemblyOptions = dynAsmOptions,
 DefaultLeaseTime = 10.Seconds(),
 PollingInterval = 5.Seconds()
 };

 try
 {
 return ShuttleDomain.Create(ref options, svContainer);
 }
 catch (Exception e)
 {
 if (e.InnerException != null)
 Console.WriteLine(e.InnerException.Message);
 else
 Console.WriteLine(e.Message);
 return null;
 }
 }
 }

 class Program
 {
 static void Main(string[] args)
 {
 
 
 
 
 ShuttleDomain.Initialize();

 
 var serviceEnd1Domain = AppDomain.CreateDomain("ServiceEndDomain1", null, null);

 
 var serviceProvider = (ServiceProvider)serviceEnd1Domain.CreateInstanceAndUnwrap
 (typeof(Program).Assembly.FullName, "JoitCode.Shuttle.SimpleSample.ServiceProvider");

 
 serviceProvider.CreateShuttleDomain();

 
 serviceProvider.RegisterServices();

 
 
 
 var str = Guid.NewGuid().ToString();
 var shuttleDomain = ShuttleDomainHelper.Create(str, str);

 
 
 
 ISimpleService service;
 if (shuttleDomain.TryGetService(out service))
 {
 try
 {
 Console.WriteLine("Currently, we are running in AppDomain {0} before calling the remote service method...", 
 AppDomain.CurrentDomain.FriendlyName);

 Console.WriteLine();

 
 var output = service.GetOutput("China");
 Console.WriteLine(output);

 Console.WriteLine();
 Console.WriteLine("Tests completed...");
 }
 catch
 {
 Console.WriteLine();
 Console.WriteLine("Failed to invoke the remote service method...");
 }
 }
 else
 {
 Console.WriteLine();
 Console.WriteLine("Failed to create remote service instance...");
 }

 
 
 
 
 shuttleDomain.ReleaseService(service);

 
 
 serviceProvider.DisposeShuttleDomain();

 Console.Read();
 }
 }
}

Here is the screenshot of result :

JointCode.Shuttle.SimpleTest

Future

JointCode.Shuttle is still a young project, and it lacks some features, such as service registering/unregistering notification, cross-AppDomain events, etc. The author will continue to improve existing functions, as well as introduce more new features in the future, but there are some limitations right now, including:

  1. Only supports 32-bit applications (x86 target platforms)
  2. Only supports Windows (only supports for .net framework, no mono support at this time)
  3. No events supported in the service interface
  4. No support for cross-AppDomain events
  5. Not thoroughly tested

LEAVE A REPLY