How can I track when the original version of the changes copied by shadow copies in the app domain

I am creating a service that hosts and runs other smaller services in separate app domains (sort of like mini IIS). When each service is registered at startup, I run the following code:

AppDomainSetup setup = new AppDomainSetup
{
    LoaderOptimization = LoaderOptimization.MultiDomain,
    ShadowCopyDirectories = service.FullPath, // Directory service binary lives in
    ShadowCopyFiles = Convert.ToString(true)
};

AppDomain domain = AppDomain.CreateDomain(service.Name, null, setup);
ServiceDomain s = domain.CreateInstanceAndUnwrap<ServiceDomain>();
s.RegisterService(service, DefaultPort);

      

Basically it sets up the application domain, allows shadow copy and calls RegisterService

in the new application domain. The method RegisterService

will create an instance of an object that will load the collected shadow into memory.

For example, a service can contain a type ServiceFoo

in an assembly Foo.dll

. Foo.dll

lives in service.FullPath

and thus gets shadowed when loading the application domain. From now on, I can delete or change the original one Foo.dll

in the directory service.FullPath

, which is great.

However, when someone modifies the original Foo.dll

(For example, they are copied to the new version over the network), I want to be notified so that I can unload the old app domain and create it again with the new build version.

Basically, what I'm trying to do here is to provide administrators with the ability to deploy new versions of services without disrupting other services running in the same process.

My question is: . How can I get notified when there is a change, Foo.dll

or perhaps any file in the directory changes ShadowCopyDirectories

? I'm sure I can check the timestamps in these directories every few seconds, but it seems like there must be a better way. What's the best approach for this situation?

Update:

My current ideas are being resolved around FileWatcher

. However, figuring out which file to watch proves my complexity.

Idea 1: Monitor service.FullPath

, which is a directory that contains a file that can be changed. However, this directory can contain multiple services. If I keep track of a directory, changing one file can lead to spurious restarts of services that don't actually use that file.

Idea 2: Parse service.TypeName

, which is a string containing the fully qualified name of the type..NET provides ways to parse those strings, for example Type.GetType(string)

, which will then allow me to access the code base. However, the second I load the assembly into the parent app domain, it becomes locked, so I can no longer modify it. I could try to parse it by TypeName

hand, but the syntax is quite complex. There are entire libraries out there trying to parse this syntax.

Idea 3: Watch the event AssemblyLoad

:

domain.AssemblyLoad += (sender, args) =>
{
    string test = AppDomain.CurrentDomain.FriendlyName;
    Uri fileUri = new Uri(args.LoadedAssembly.CodeBase);
    FileInfo fileInfo = new FileInfo(fileUri.LocalPath);
};

      

This is triggered when the domain loads the assembly. I can determine if it fileInfo.Directory

is the same as service.FullPath

and adjust the clock in that file if it is. One problem. This delegate runs in context domain

. I don't have access to ServiceResolver

or anything in the root domain.

Idea 4: After I call s.RegisterService

, check off domain.GetAssemblies()

and try to find the ones that came from service.FullPath

. However, when I run:

var assemblies = domain.GetAssemblies();

      

It throws an exception immediately:

Enter 'System.Reflection.Emit.InternalAssemblyBuilder' in assembly 'mscorlib, Version = 4.0.0.0, Culture = neutral, PublicKeyToken = b77a5c561934e089' is not marked serializable.

I don't quite understand why this is the case. I'm guessing it's GetAssemblies()

only meant to call the current AppDomain.

+3


source to share


1 answer


So here's what I did. First, I created a new class called ServiceDomainProxy

, which looks like this:

internal class ServiceDomainProxy : MarshalByRefObject
{
    private ServiceController controller;
    private List<FileSystemWatcher> watchers;

    public ServiceDomainProxy(ServiceController controller)
    {
        this.controller = controller;
    }

    public void CreateMonitor(Uri fileUri)
    {
        if(watchers == null)
            watchers = new List<FileSystemWatcher>();

        // Setup a FileWatcher on type, notify controller whenever it reloads
        FileInfo file = new FileInfo(fileUri.LocalPath);
        FileSystemWatcher watcher = new FileSystemWatcher
        {
            Path = file.DirectoryName,
            Filter = file.Name
        };

        watcher.Changed += fileChanged;
        watcher.EnableRaisingEvents = true;
        watchers.Add(watcher);
    }

    void fileChanged(object sender, FileSystemEventArgs e)
    {
        Controller.Log.Info("Reloading Service Assembly: {0}", e.Name);
        // TODO: Call the Reload method on controller
    }
}

      

This class basically acts as a proxy between domains. Then I changed my class ServiceDomain

to have a constructor that takes an instance ServiceDomainProxy

:

public ServiceDomain(ServiceDomainProxy proxy)
{
    this.proxy = proxy;
}

      

I need to update my call CreateInstanceAndUnwrap

to pass on an instance ServiceDomainProxy

:



Type t = typeof(ServiceDomain);
ServiceDomain s = domain.CreateInstanceAndUnwrap(t.Assembly.FullName, t.FullName, false, BindingFlags.Default, null, new object[] { proxy }, null, null);

      

Then in ServiceDomain.RegisterService

I can register the allowed type with ServiceDomainProxy

:

proxy.CreateMonitor(new Uri(service.ResolveType().Assembly.CodeBase));

      

This calls the original instance ServiceDomainProxy

through the application domain constraints, which can then configure the file system monitor and alert the controller to restart the service.

Hope this helps someone!

0


source







All Articles