File: System\Addin\Hosting\AddInControllerImpl.cs
Project: ndp\fx\src\AddIn\AddIn\System.AddIn.csproj (System.AddIn)
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
/*============================================================
**
** Class:  AddInController
**
** Purpose: Allows you to shut down an add-in, which may unload
**     an AppDomain or kill an out-of-process add-in.  
**
===========================================================*/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Reflection;
using System.Runtime.ConstrainedExecution;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Lifetime;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.Security;
using System.Diagnostics;
using System.AddIn.Contract;
using System.AddIn.Pipeline;
using System.Diagnostics.Contracts;
 
namespace System.AddIn.Hosting
{
    // The lifetime of an add-in is controlled by remoting's leases, and users
    // who do essentially ref counting by obtaining a lease.  No one has solved
    // the distributed (cross-machine) garbage collector problem yet, but leases
    // appear to be the best approximation we currently have.  
    internal sealed class AddInControllerImpl
    {
        // Maps host add-in views (precisely, instances of host adapters typed
        // as their base type) to the associated add-in controllers.
        private static HAVControllerPair _havList;
        private static readonly Object _havLock = new Object();
 
        //private AppDomain _appDomain;
        private bool _unloadDomainOnExit;
        private AddInToken _token;
 
        private AddInEnvironment _addInEnvironment;
 
        private static int _addInCountSinceLastPrune;
        private const int AddInCountSinceLastPruneLimit = 25;
 
        // Note that keeping this reference to the transparentProxy 
        // prevents it from being GC'd in the same sweep as the HAV.  Indeed, it cannot
        // be GC'd until we remove its HAVController pair.  This is good,
        // since it allows us to call into the TransparentProxy from the finalize method
        // of LifetimeTokenHandle
        internal IContract _contract;  // Contract or a Transparent Proxy to the contract 
        private ActivationWorker _activationWorker;
        private WeakReference _havReference;
 
        internal AddInControllerImpl(AddInEnvironment environment, bool unloadDomainOnExit, AddInToken token)
        {
            System.Diagnostics.Contracts.Contract.Requires(environment != null);
            System.Diagnostics.Contracts.Contract.Requires(token != null);
 
            _unloadDomainOnExit = unloadDomainOnExit;
            _token = token;
 
            _addInEnvironment = environment;
        }
 
        // Takes a host add-in view (HAV) and maps that to an add-in controller.
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        internal static AddInController GetAddInController(Object addIn)
        {
            if (addIn == null)
                throw new ArgumentNullException("addIn");
            System.Diagnostics.Contracts.Contract.EndContractBlock();
 
            AddInControllerImpl controllerImpl = FindController(addIn, false);
 
            //return new wrapper
            if (controllerImpl != null)
            {
                // Try and increase the ref count on the addin.  If we fail, perhaps
                // because the user already called Dispose() on the HVA, that's OK.  Still allow
                // them to use the AddInController to examine the AddInToken 
                ContractHandle handle = null;
                try
                {
                     handle = new ContractHandle(controllerImpl._contract);
                }
                catch (Exception) {}
                return new AddInController(controllerImpl, addIn, handle);
            }
 
            throw new ArgumentException(Res.ControllerNotFound);
        }
 
        // Find the controller given the HAV (addIn), optionally also removing it.
        // This code also removes any stale HAVControllerPairs that it finds, as
        // may happen when a HAV is garbage collected.
        private static AddInControllerImpl FindController(Object addIn, bool remove)
        {
            System.Diagnostics.Contracts.Contract.Requires(addIn != null);
 
            lock (_havLock)
            {
                HAVControllerPair current = _havList;
                HAVControllerPair last = null;
                while(current != null)
                {
                    Object o = current._HAV.Target;
                    if (o == null)
                    {
                        // this one has been GC'd.  Clean up the WR
                        if (last == null)
                        {
                            _havList = current._next;
                            continue;
                        }
                        else
                        {
                            last._next = current._next;
                            current = current._next;
                            continue;
                        }
                    }
                    else
                    {
                        if (addIn.Equals(o))
                        {
                            AddInControllerImpl value = current._controller;
                            if (remove)
                            {
                                if (last == null)
                                    _havList = current._next;
                                else
                                    last._next = current._next;
                            }
                            return value;
                        }
                    }
 
                    last = current;
                    current = current._next;
                }
            }
            return null;
        }
 
        // Requires the HAV and the transparent proxy to the add-in adapter, 
        // which implements System.AddIn.Contract.IContract.
        internal void AssociateWithHostAddinView(Object hostAddinView, IContract contract)
        {
            System.Diagnostics.Contracts.Contract.Requires(hostAddinView != null);
            _contract = contract;
 
            // add weak reference on the HAV to our list
            _havReference = new WeakReference(hostAddinView);
            lock (_havLock)
            {
                HAVControllerPair havRef = new HAVControllerPair(hostAddinView, this);
                havRef._next = _havList;
                _havList = havRef;
 
                // clean up the list every so often
                _addInCountSinceLastPrune++;
                if (_addInCountSinceLastPrune == AddInCountSinceLastPruneLimit)
                {
                    // searching for a non-exiting addin will have the desired effect
                    // of pruning the list of any stale references.
                    FindController(new Object(), false);
                    _addInCountSinceLastPrune = 0;
                }
            }
        }
 
        internal ActivationWorker ActivationWorker {
            set {
                System.Diagnostics.Contracts.Contract.Requires(value != null);
                _activationWorker = value;
            }
        }
 
        //<SecurityKernel Critical="True" Ring="0">
        //<ReferencesCritical Name="Method: ActivationWorker.CreateAddInAdapter(System.Object,System.Reflection.Assembly):System.AddIn.Contract.IContract" Ring="1" />
        //<Asserts Name="Imperative: System.Security.PermissionSet" />
        //</SecurityKernel>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity", Justification = "Reviewed"),
         System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom", Justification = "LoadFrom was designed for addins")]
        [System.Security.SecuritySafeCritical]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2128:SecurityTransparentCodeShouldNotAssert", Justification = "This is a SecurityRules.Level1 assembly, in which this rule is being incorrectly applied")]
        internal IContract GetContract()
        {
            if (_contract != null)
                return _contract;
 
            // in direct connect, the contract has not been created.  Create it now.
            Object hav = _havReference.Target;
            if (hav == null)
                throw new InvalidOperationException(Res.AddInNoLongerAvailable);
 
            // Assert permission to the contracts, AddInSideAdapters, AddInViews and specific Addin directories only.
            PermissionSet permissionSet = new PermissionSet(PermissionState.None);
            permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery,
                Path.Combine(_token.PipelineRootDirectory, AddInStore.ContractsDirName)));
            permissionSet.AddPermission(new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.PathDiscovery,
                Path.Combine(_token.PipelineRootDirectory, AddInStore.AddInAdaptersDirName)));
            permissionSet.Assert();
 
            Assembly.LoadFrom(_token._contract.Location);
            Assembly addinAdapterAssembly = Assembly.LoadFrom(_token._addinAdapter.Location);
            CodeAccessPermission.RevertAssert();
 
            // Create the AddIn adapter for the addin
            ActivationWorker worker = new ActivationWorker(_token);
            _contract = worker.CreateAddInAdapter(hav, addinAdapterAssembly);
            return _contract;
        }
 
        // <SecurityKernel Critical="True" Ring="1">
        // <ReferencesCritical Name="Method: ActivationWorker.Dispose():System.Void" Ring="1" />
        // </SecurityKernel>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.GC.Collect", Justification = "Recommended by GC team")]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public void Shutdown()
        {
            // Disables usage of the add-in, by breaking the pipeline.
            // Also, if we own the appdomain, we unload it.
            lock (this)  // Ensure multiple threads racing on Shutdown don't collide.
            {
                AddInEnvironment environment = _addInEnvironment;
                if (environment != null) {
                    try {
                        if (_contract != null) {
                            
                            Object hav = _havReference.Target;
                            IDisposable disposableHAV = hav as IDisposable;
                            if (disposableHAV != null)
                            {
                                try
                                {
                                    disposableHAV.Dispose();
                                }
                                catch (AppDomainUnloadedException e)
                                {
                                    Log(e.ToString());
                                }
                                catch (RemotingException re)
                                {
                                    Log(re.ToString());
                                }
                                catch (SerializationException se)
                                {
                                    Log(se.ToString());
                                }
                            }
 
                            IDisposable disposableContract = _contract as IDisposable;
                            if (disposableContract != null)
                            {
                                try
                                {
                                    disposableContract.Dispose();
                                }
                                catch (AppDomainUnloadedException e)
                                {
                                    Log(e.ToString());
                                }
                                catch (RemotingException re)
                                {
                                    Log(re.ToString());
                                }
                                catch (SerializationException se)
                                {
                                    Log(se.ToString());
                                }
                            }
                            _contract = null;
                        }
 
                        if (_activationWorker != null) {
                            // Unhook an assembly resolve event in the target appdomain.
                            // However, if one of the adapters implemented IDisposable and cleaned
                            // up the appropriate lifetime tokens, this appdomain may be unloading
                            // already (we launch another thread to do this, so we are guaranteed
                            // to have a benign race condition).  We should catch an 
                            // AppDomainUnloadedException here.
                            try
                            {
                                _activationWorker.Dispose();
                            }
                            catch (AppDomainUnloadedException) { }
                            catch (RemotingException) { }
                            catch (SerializationException) { }
 
                            _activationWorker = null;
                        }
                    }
                    finally {
                        if (_unloadDomainOnExit) {
                            // AppDomain.Unload will block until we have finalized all 
                            // objects within the appdomain.  Also, this may already
                            // have been unloaded.
                            try {
                                environment.UnloadAppDomain();
                            }
                            catch (AppDomainUnloadedException) { }
                            catch (RemotingException) { }
 
                            // Using the transparent proxy will now cause exceptions, 
                            // as managed threads are not allowed to enter this appdomain.
                        }
                    }
                    _addInEnvironment = null;
 
                    // eagerly remove from list
                    lock (_havLock)
                    {
                        Object addin = _havReference.Target;
                        if (addin != null)
                            FindController(addin, true);
                    }
 
                    // The perf team recommends doing a GC after a large amount of memory has
                    // been dereferenced.  We wait for the finalizers to complete first
                    // becase some references in the addin are not released until finalization.
                    // Also, if an addin is buggy and causes the finalizer thread to hang, 
                    // waiting here makes it fail deterministically. 
                    System.GC.WaitForPendingFinalizers();
                    System.GC.Collect();
 
                } // end if domain != null
                else {
                    throw new InvalidOperationException(Res.AppDomainNull);
                } 
            }
        }
 
        // This will not be usable for OOP scenarios.  
        internal AppDomain AppDomain {
            get {
                if (_addInEnvironment == null)
                    throw new ObjectDisposedException("appdomain");
                return _addInEnvironment.AppDomain;
            }
        }
 
        internal AddInToken Token {
            get {
                return _token;
            }
        }
 
        internal AddInEnvironment AddInEnvironment
        {
            get {
                return _addInEnvironment;
            }
        }
 
        private static void Log(String message)
        {
            Debugger.Log(0, "AddInController", message);
        }
 
        internal sealed class HAVControllerPair
        {
            internal WeakReference _HAV;
            internal AddInControllerImpl _controller;
            internal HAVControllerPair _next;
 
            public HAVControllerPair(Object hav, AddInControllerImpl controller)
            {
                _HAV = new WeakReference(hav);
                _controller = controller;
            }
        }
    }
}