Programing

AppDomain 및 MarshalByRefObject 수명 : RemotingException을 피하는 방법?

crosscheck 2020. 12. 31. 22:49
반응형

AppDomain 및 MarshalByRefObject 수명 : RemotingException을 피하는 방법?


MarshalByRef 개체가 AppDomain (1)에서 다른 개체 (2)로 전달 될 때 두 번째 AppDomain (2)에서 메서드를 호출하기 전에 6 분 동안 기다리면 RemotingException이 발생합니다.

System.Runtime.Remoting.RemotingException : 개체 [...]의 연결이 끊어 졌거나 서버에 없습니다.

이 isse에 대한 몇 가지 문서 :

내가 틀렸다면 정정하십시오. InitializeLifetimeService가 null을 반환하면 프록시가 수집 된 경우에도 AppDomain 2가 언로드 될 때 AppDomain 1에서만 개체를 ​​수집 할 수 있습니까?

수명을 비활성화하고 프록시 (AppDomain 2) 및 개체 (AppDomain1)를 프록시가 완료 될 때까지 활성 상태로 유지하는 방법이 있습니까? ISponsor와 함께라면 ...?


여기에서 대답을 참조하십시오.

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

기본적으로 다음과 같이 말합니다.

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

마침내 클라이언트 활성화 인스턴스를 수행하는 방법을 찾았지만 Finalizer에 관리 코드가 포함되어 있습니다. (저는 CrossAppDomain 통신을 위해 제 클래스를 전문화했지만 수정하고 다른 원격 작업을 시도 할 수 있습니다. 버그를 발견하면 알려주십시오.

다음 두 클래스는 관련된 모든 응용 프로그램 도메인에로드 된 어셈블리에 있어야합니다.

  /// <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));
    }
  }

이제 CrossAppDomainObject, 원격 개체는 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);
    }
  }

불행히도이 솔루션은 AppDomains가 플러그인 용도로 사용되는 경우 잘못되었습니다 (플러그인 어셈블리를 기본 appdomain에로드하면 안 됨).

생성자 및 소멸자의 GetRealObject () 호출은 원격 개체의 실제 유형을 가져 와서 원격 개체의 어셈블리를 현재 AppDomain에로드하려고합니다. 이로 인해 예외 (어셈블리를로드 할 수없는 경우) 또는 나중에 언로드 할 수없는 외부 어셈블리를로드 한 원치 않는 효과가 발생할 수 있습니다.

더 나은 솔루션은 ClientSponsor.Register () 메서드를 사용하여 기본 AppDomain에 원격 개체를 등록하는 것입니다 (정적이지 않으므로 클라이언트 스폰서 인스턴스를 만들어야 함). 기본적으로 2 분마다 원격 프록시를 갱신합니다. 객체의 수명이 기본 5 분이면 충분합니다.


파괴시 연결이 끊어지는 클래스를 만들었습니다.

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

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

여기에는 두 가지 가능한 해결책이 있습니다.

Singleton 접근 방식 : InitializeLifetimeService 재정의

Sacha Goldshtein 이 원본 포스터에 연결된 블로그 게시물에서 지적 했듯이 Marshaled 개체에 Singleton 의미 체계가있는 경우 재정의 할 수 있습니다 InitializeLifetimeService.

class MyMarshaledObject : MarshalByRefObject
{
    public bool DoSomethingRemote() 
    {
      // ... execute some code remotely ...
      return true; 
    }

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

그러나 user266748이 다른 답변 에서 지적했듯이

클라이언트가 자체적으로 연결할 때마다 그러한 객체가 생성되면 해당 솔루션은 작동하지 않습니다. 왜냐하면 그것들은 결코 GCed되지 않을 것이고 서버를 중지하거나 더 이상 메모리가 없기 때문에 충돌 할 때까지 메모리 소비가 증가 할 것이기 때문입니다.

클래스 기반 접근 방식 : ClientSponsor 사용

보다 일반적인 솔루션은 ClientSponsor클래스 활성화 원격 개체의 수명을 연장하는 데 사용 하는 것입니다. 링크 된 MSDN 문서에는 따라갈 수있는 유용한 시작 예제가 있습니다.

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Lifetime;
namespace RemotingSamples
{

   class HelloClient
   {
       static void Main()
      {
         // Register a channel.
         TcpChannel myChannel = new TcpChannel ();
         ChannelServices.RegisterChannel(myChannel);
         RemotingConfiguration.RegisterActivatedClientType(
                                typeof(HelloService),"tcp://localhost:8085/");

         // Get the remote object.
         HelloService myService = new HelloService();

         // Get a sponsor for renewal of time.
         ClientSponsor mySponsor = new ClientSponsor();

         // Register the service with sponsor.
         mySponsor.Register(myService);

         // Set renewaltime.
         mySponsor.RenewalTime = TimeSpan.FromMinutes(2);

         // Renew the lease.
         ILease myLease = (ILease)mySponsor.InitializeLifetimeService();
         TimeSpan myTime = mySponsor.Renewal(myLease);
         Console.WriteLine("Renewed time in minutes is " + myTime.Minutes.ToString());

         // Call the remote method.
         Console.WriteLine(myService.HelloMethod("World"));

         // Unregister the channel.
         mySponsor.Unregister(myService);
         mySponsor.Close();
      }
   }
}

Remoting API에서 수명 관리가 작동하는 방식은 가치가 없습니다 . 여기 MSDN에서 잘 설명 됩니다. 가장 유용하다고 생각되는 부분을 인용했습니다.

The remoting lifetime service associates a lease with each service, and deletes a service when its lease time expires. The lifetime service can take on the function of a traditional distributed garbage collector, and it also adjusts well when the numbers of clients per server increases.

Each application domain contains a lease manager that is responsible for controlling leases in its domain. All leases are examined periodically for expired lease times. If a lease has expired, one or more of the lease's sponsors are invoked and given the opportunity to renew the lease. If none of the sponsors decides to renew the lease, the lease manager removes the lease and the object can be collected by the garbage collector. The lease manager maintains a lease list with leases sorted by remaining lease time. The leases with the shortest remaining time are stored at the top of the list. The remoting lifetime service associates a lease with each service, and deletes a service when its lease time expires.


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.


[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 ;)


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);
    }
}

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.

ReferenceURL : https://stackoverflow.com/questions/2410221/appdomain-and-marshalbyrefobject-life-time-how-to-avoid-remotingexception

반응형