Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inter-AppDomain communication problem

I've been developing a Windows Service in C#.

A set of configuration file paths is supplied to this service when it starts. For each of these files the service will spin up an AppDomain using the file as its ConfigurationFile and the folder of this file as the ApplicationBase. Each folder will have a "bin" folder that is set as PrivateBinPath.

The "bin" folder in these folders contain a small assembly that is shared in common with the service, this assembly contains the interface IServiceHost. Also the type name and assembly name of a class that implements the IServiceHost interface is known.

The whole CreateServiceHost method looks like this:-

    public static IServiceHost CreateServiceHost(string configPath, string entryAssembly, string entryType)
    {
        IServiceHost host;

        AppDomainSetup setupInfo = new AppDomainSetup();
        setupInfo.ApplicationBase = Path.GetDirectoryName(configPath);
        setupInfo.PrivateBinPath = Path.Combine(setupInfo.ApplicationBase, "bin");
        setupInfo.ShadowCopyFiles = "true";
        setupInfo.ConfigurationFile = configPath;

        AppDomain appDomain = AppDomain.CreateDomain("Service for: " + setupInfo.ApplicationBase, AppDomain.CurrentDomain.Evidence, setupInfo);


        object objHost = appDomain.CreateInstanceFromAndUnwrap(Path.Combine(setupInfo.PrivateBinPath, entryAssembly), entryType);
        host = (IServiceHost)objHost;

        return host;
    }

The IServiceHost interface is incredibly complex:-

public interface IServiceHost
{
    void Start();
    void Stop();
}

The service OnStart contains something like this:-

private List<IServiceHost> serviceHosts = new List<IServiceHost>();

protected override void OnStart(string[] args)
{
    foreach (string configPaths in GetConfigPaths())
    {
        IServiceHost host = ServiceHostLoader.CreateServiceHost(configPath);
        serviceHosts.Add(host);
        host.Start();
    }
}

The OnStop is equally straight-forward (for now to keep things simple the IServiceHost.Stop are blocking calls).

protected override void OnStop()
{
    foreach (IServiceHost host in serviceHosts)
    {
        host.Stop();
    }
}

This all simple enough and it works fine when testing on development machines. However in QA I'm getting exceptions when it is stopped. When in development we spin things up only for a short period it all seems to work fine. However in QA the service is only stopped every 24 hours. In this case it consistently fails to stop correctly.

Here is an example of what ends up in the Event log:-

Event Type: Error Event Source: Workspace Services Event Category: None Event ID: 0 Date: 11/03/2011 Time: 08:00:00 User: N/A Computer: QA-IIS-01 Description: Failed to stop service. System.Runtime.Remoting.RemotingException: Object '/50e76ee1_3f40_40a1_9311_1256a0375f7d/msjxeib0oy+s0sog1mkeikjd_2.rem' has been disconnected or does not exist at the server.

Server stack trace: at System.Runtime.Remoting.Channels.ChannelServices.CheckDisconnectedOrCreateWellKnownObject(IMessage msg) at System.Runtime.Remoting.Channels.ChannelServices.SyncDispatchMessage(IMessage msg)

Exception rethrown at [0]: at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg) at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type) at MyOrg.Service.IServiceHost.Stop() at MyOrg.Workspace.Service.MyAppService.OnStop() at System.ServiceProcess.ServiceBase.DeferredStop()

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.

Now for test purposes the actual IServiceHost simply posts entries to the event log as a heart beat and entries indicating start up and stop and I'm only spinning up a single AppDomain.

It would seem that over time the remote proxy for the implementer of IServiceHost in the main service default app domain has lost touch with its other end in the generated domain.

Can anyone explain why that is happening, or offer a better way for the default domain to ask the generated domains to shutdown in a tidy manner?

like image 427
AnthonyWJones Avatar asked Mar 11 '11 16:03

AnthonyWJones


1 Answers

A stab in the dark here. Is the lifetime lease on the remote object expiring? Look into MarshalByRefObject.InitializeLifetimeService. To make the object persistent, just override and return null.

public override object InitializeLifetimeService()
{
    // returning null here will prevent the lease manager
    // from deleting the object.
    return null;
}
like image 106
Jim Mischel Avatar answered Oct 31 '22 20:10

Jim Mischel