AppDomain and MarshalByRefObject life time : how t

2019-01-12 20:48发布

问题:

When a MarshalByRef object is passed from an AppDomain (1) to another (2), if you wait 6 mins before calling a method on it in the second AppDomain (2) you will get a RemotingException :

System.Runtime.Remoting.RemotingException: Object [...] has been disconnected or does not exist at the server.

Some documentation about this isse :

  • http://blogs.microsoft.co.il/blogs/sasha/archive/2008/07/19/appdomains-and-remoting-life-time-service.aspx
  • http://blogs.msdn.com/cbrumme/archive/2003/06/01/51466.aspx - Instance Lifetime, cbrumme says "We should fix this." :(

Correct me if I'm wrong : if InitializeLifetimeService returns null, the object can only be collected in AppDomain 1 when AppDomain 2 is Unloaded, even if the proxy was collected ?

Is there a way to disable life time and keep the proxy (in AppDomain 2) and the object (in AppDomain1) alive until the proxy is Finalized ? Maybe with ISponsor... ?

回答1:

see answer here:

http://social.msdn.microsoft.com/Forums/en-US/netfxremoting/thread/3ab17b40-546f-4373-8c08-f0f072d818c9/

which basically says:

[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.Infrastructure)]
public override object InitializeLifetimeService()
{
  return null;
}


回答2:

I finally found a way to do client activated instances but it involves managed code in Finalizer :( I specialized my class for CrossAppDomain communication but you may modify it and try in others remoting. Let me know if you find any bug.

The two following classes must be in an assembly loaded in all application domains involved.

  /// <summary>
  /// Stores all relevant information required to generate a proxy in order to communicate with a remote object.
  /// Disconnects the remote object (server) when finalized on local host (client).
  /// </summary>
  [Serializable]
  [EditorBrowsable(EditorBrowsableState.Never)]
  public sealed class CrossAppDomainObjRef : ObjRef
  {
    /// <summary>
    /// Initializes a new instance of the CrossAppDomainObjRef class to
    /// reference a specified CrossAppDomainObject of a specified System.Type.
    /// </summary>
    /// <param name="instance">The object that the new System.Runtime.Remoting.ObjRef instance will reference.</param>
    /// <param name="requestedType"></param>
    public CrossAppDomainObjRef(CrossAppDomainObject instance, Type requestedType)
      : base(instance, requestedType)
    {
      //Proxy created locally (not remoted), the finalizer is meaningless.
      GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Initializes a new instance of the System.Runtime.Remoting.ObjRef class from
    /// serialized data.
    /// </summary>
    /// <param name="info">The object that holds the serialized object data.</param>
    /// <param name="context">The contextual information about the source or destination of the exception.</param>
    private CrossAppDomainObjRef(SerializationInfo info, StreamingContext context)
      : base(info, context)
    {
      Debug.Assert(context.State == StreamingContextStates.CrossAppDomain);
      Debug.Assert(IsFromThisProcess());
      Debug.Assert(IsFromThisAppDomain() == false);
      //Increment ref counter
      CrossAppDomainObject remoteObject = (CrossAppDomainObject)GetRealObject(new StreamingContext(StreamingContextStates.CrossAppDomain));
      remoteObject.AppDomainConnect();
    }

    /// <summary>
    /// Disconnects the remote object.
    /// </summary>
    ~CrossAppDomainObjRef()
    {
      Debug.Assert(IsFromThisProcess());
      Debug.Assert(IsFromThisAppDomain() == false);
      //Decrement ref counter
      CrossAppDomainObject remoteObject = (CrossAppDomainObject)GetRealObject(new StreamingContext(StreamingContextStates.CrossAppDomain));
      remoteObject.AppDomainDisconnect();
    }

    /// <summary>
    /// Populates a specified System.Runtime.Serialization.SerializationInfo with
    /// the data needed to serialize the current System.Runtime.Remoting.ObjRef instance.
    /// </summary>
    /// <param name="info">The System.Runtime.Serialization.SerializationInfo to populate with data.</param>
    /// <param name="context">The contextual information about the source or destination of the serialization.</param>
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
      Debug.Assert(context.State == StreamingContextStates.CrossAppDomain);
      base.GetObjectData(info, context);
      info.SetType(typeof(CrossAppDomainObjRef));
    }
  }

And now the CrossAppDomainObject, your remoted object must inherit from this class instead of MarshalByRefObject.

  /// <summary>
  /// Enables access to objects across application domain boundaries.
  /// Contrary to MarshalByRefObject, the lifetime is managed by the client.
  /// </summary>
  public abstract class CrossAppDomainObject : MarshalByRefObject
  {
    /// <summary>
    /// Count of remote references to this object.
    /// </summary>
    [NonSerialized]
    private int refCount;

    /// <summary>
    /// Creates an object that contains all the relevant information required to
    /// generate a proxy used to communicate with a remote object.
    /// </summary>
    /// <param name="requestedType">The System.Type of the object that the new System.Runtime.Remoting.ObjRef will reference.</param>
    /// <returns>Information required to generate a proxy.</returns>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public sealed override ObjRef CreateObjRef(Type requestedType)
    {
      CrossAppDomainObjRef objRef = new CrossAppDomainObjRef(this, requestedType);
      return objRef;
    }

    /// <summary>
    /// Disables LifeTime service : object has an infinite life time until it's Disconnected.
    /// </summary>
    /// <returns>null.</returns>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public sealed override object InitializeLifetimeService()
    {
      return null;
    }

    /// <summary>
    /// Connect a proxy to the object.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public void AppDomainConnect()
    {
      int value = Interlocked.Increment(ref refCount);
      Debug.Assert(value > 0);
    }

    /// <summary>
    /// Disconnects a proxy from the object.
    /// When all proxy are disconnected, the object is disconnected from RemotingServices.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public void AppDomainDisconnect()
    {
      Debug.Assert(refCount > 0);
      if (Interlocked.Decrement(ref refCount) == 0)
        RemotingServices.Disconnect(this);
    }
  }


回答3:

Unfortunately this solution is wrong when AppDomains are used for plugin purposes (assembly of the plugin must not be loaded into your main appdomain).

The GetRealObject() call in your constructor and destructor results in obtaining the real type of the remote object, which leads to trying to load the assembly of the remote object into the current AppDomain. This may cause either an exception (if the assembly cannot be loaded) or the unwanted effect that you have loaded a foreign assembly that you cannot unload later.

A better solution can be if you register your remote objects in your main AppDomain with ClientSponsor.Register() method (not static so you must create a client sponsor instance). By default it will renew your remote proxies in every 2 minutes, which is enough if your objects has the default 5 minutes lifetime.



回答4:

I created a class which disconnect on destruction.

public class MarshalByRefObjectPermanent : MarshalByRefObject
{
    public override object InitializeLifetimeService()
    {
        return null;
    }

    ~MarshalByRefObjectPermanent()
    {
        RemotingServices.Disconnect(this);
    }
}


回答5:

You could try a serializable singleton ISponsor object implementing IObjectReference. The GetRealObject implementation (from IObjectReference should return MySponsor.Instance when context.State is CrossAppDomain, otherwise return itself. MySponsor.Instance is a self-initializing, synchronized (MethodImplOptions.Synchronized), singleton. The Renewal implementation (from ISponsor) should check a static MySponsor.IsFlaggedForUnload and return TimeSpan.Zero when flagged for unload/AppDomain.Current.IsFinalizingForUnload() or return LifetimeServices.RenewOnCallTime otherwise.

To attach it, simply obtain an ILease and Register(MySponsor.Instance), which will be transformed into the MySponsor.Instance set within the AppDomain due to the GetRealObject implementation.

To stop sponsorship, re-obtain the ILease and Unregister(MySponsor.Instance), then set the MySponsor.IsFlaggedForUnload via a cross-AppDomain callback (myPluginAppDomain.DoCallback(MySponsor.FlagForUnload)).

This should keep your object alive in the other AppDomain until either the unregister call, the FlagForUnload call, or AppDomain unload.



回答6:

[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.Infrastructure)]
public override object InitializeLifetimeService()
{
  return null;
}

I've tested this one and its working fine, of course one has to know that the proxy lives forever, until you do GC-ing for yourself. But i my case, using a Plugin-Factory connected to my main app, there is no memory leak or something like this. I just made sure, that i'm implementing IDisposable and it's working fine (I can tell, because my loaded dll's (in the factory) can be overwriten once the factory is disposed correctly)

Edit: If your bubbling events through the domains, add this line of code to the Class creating the proxy as well, otherwise your bubbling will throw too ;)



回答7:

If you would like to re-create the remote object after it has been garbage collected without having to create an ISponsor class nor giving it infinite lifetime, you could call a dummy function of the remote object while catching RemotingException.

public static class MyClientClass
{
    private static MarshalByRefObject remoteClass;

    static MyClientClass()
    {
        CreateRemoteInstance();
    }

    // ...

    public static void DoStuff()
    {
        // Before doing stuff, check if the remote object is still reachable
        try {
            remoteClass.GetLifetimeService();
        }
        catch(RemotingException) {
            CreateRemoteInstance(); // Re-create remote instance
        }

        // Now we are sure the remote class is reachable
        // Do actual stuff ...
    }

    private static void CreateRemoteInstance()
    {
        remoteClass = (MarshalByRefObject)AppDomain.CurrentAppDomain.CreateInstanceFromAndUnwrap(remoteClassPath, typeof(MarshalByRefObject).FullName);
    }
}


回答8:

I recently ran into this exception also. Right now my solution is just unload AppDomain and then reload AppDomain after a long interval. Luckily this temporary solution work for my case. I wish there is a more elegant way to deal with this.