Lease Management - GenuineChannels/GenuineChannels GitHub Wiki

There are several ways to release acquired resources after clients stop using them. My favorite one is described in this article.

By Dmitry Belikov

You can find a good general description of lease concept here, section "Object Lifetime with Leasing". Take a look at the conclusion.

MSDN says

Providing a perfect remoting framework that meets the needs of the majority of business applications is certainly a difficult, if not impossible, endeavor. By providing a framework that can be extended and customized as required, Microsoft has taken a key step in the right direction.

Here we continue moving towards the right direction!

First, let’s mention several moments.

  1. If you need to get rid of lease stuff for a specific MBR object (useful for the factory pattern), just override the InitializeLifetimeService member and return a null reference. In this case the object will never be disconnected from .NET Remoting.
public class ObjectFactoryImplementation : MarshalByRefObject
{
  // ...  

  /// <summary>
  /// This is to insure that when created as a Singleton,
  /// the first instance never dies,
  /// regardless of the expired time.
  /// </summary>
  /// <returns>A null value.</returns>
  public override object InitializeLifetimeService()
  {
    return null;
  }
}
  1. If you need to hold on an object located on the remote host, create an instance of the ClientSponsor class (System.Runtime.Remoting.LifeTime.ClientSponsor) and register it. The remote MBR object will not become a subject to Garbage Collection until this ClientSponsor’s instance is alive:
IKnownBusinessObjectFactory iKnownBusinessObjectFactory =
      (IKnownBusinessObjectFactory) 
       Activator.GetObject(typeof(IKnownBusinessObjectFactory),
       ConfigurationSettings.AppSettings["RemoteHostUri"] + 
                                 "/ObjectFactory.rem");

IKnownBusinessObject iKnownBusinessObject = 
       iKnownBusinessObjectFactory.GetBusinessObject();

// attach the sponsor
ClientSponsor сlientSponsor = new ClientSponsor();
ILease lease = (ILease) RemotingServices.GetLifetimeService
       ((MarshalByRefObject) iKnownBusinessObject);
lease.Register(сlientSponsor);
  1. Remember that lease management does not deal with references. Lease management prevents an object from being garbage collected until lease expires. A reference to an MBR object does not let Garbage Collection to release it, but such a reference does not prevent an MBR object from being disconnected from .NET Remoting. If it happens, you will have a reference to an existent object, but it will be impossible to invoke it from a remote host.
  2. If you decide to develop your own sponsor implementing the ISponsor interface, you should know that such a sponsor itself must be an instance of the MarshalByRefObject-derived class and, therefore, it automatically falls under lifetime management as well. If you do not override InitializeLifetimeService or specify endless lease expire time, then your sponsor will be disconnected and just stop working. Such problems are hard for revealing, so be careful. I would recommend always to use instances of the ClientSponsor class.
  3. Notice that it is not necessary to unregister a sponsor from all obtained MBR objects. Simply set the ClientSponsor.RenewalTime property to the TimeSpan.Zero value. In this case the sponsor stops renewing remote objects and let them die. Also it’s a good idea to register one and the same ClientSponsor instance to all obtained MBR objects related to one task. In this case this simple assignment will release all obtained resources.
  4. If you’re going to perform regular actions, copying a file is a good example for this, then you do not need to care about sponsorship at all. Lease is renewed each time you make an invocation.

Disposing resources

It is very important for the server implementation to manage its resources right. Usually the problem looks like this. A business object acquires some resources and those resources should be guaranteed to be deallocated after clients stop using it. Waiting for a Garbage Collection cycle can be inadmissible due to unpredictable timing and the importance of resources, and, more importantly, this can happen only if you do not keep any references to this business object on the server itself.

Here I will try to show how it is implemented in my client-server solutions that use .NET Remoting. This approach does not rely on Genuine Channels functionality and can be useful if you use native channels as well.

A note to my customers. I would not recommend to keep track of connection closed events such as GeneralConnectionClosed or HostResourcesReleased for these purposes. Retrieving the URI of the remote host and releasing appropriate business objects acquired by the disconnected client requires much more programming and higher responsibility. You will have to remember different things in Client Session and analyze them on the connection closed event. It will be better to use this approach instead.

First, figure the issue in a more detailed way. You've created a business object and a client (or some clients) has a transparent proxy pointing to this object. The clients have registered sponsors holding on the business object and this object will not be destroyed until all sponsors stop renewing the object lease and Garbage Collection decides to release it. Here you understand that after all sponsors stop renewing the lease, the object will not receive any signal or event indicating that the object is not in use anymore! It is great if you keep no references to the business object, so it has a chance to be released during Garbage Collection cycle. Note that usually long-living objects are gathered in the last generation; usually it takes some time until Garbage Collection performs the full cycle. Therefore, in this case you can implement any resource clean-up functionality in the destructor. But what if you keep references to business objects somewhere on the server?

I hope the problem is clear. The following approach allows you to receive a signal indicating that the objects has been disconnected from .NET Remoting, so nobody has a chance to call it remotely anymore. You can implement any logic on this signal; remove this business object from your internal collections; release resources and so on.

You can download the source code here. This approach is very easy in use and its basic idea is pretty simple, though the implementation may seem non-trivial. Business object classes inherit the MarshalByRefObject class. The MarshalByRefObject.GetLifetimeService method returns an instance of the ILease class, whose CurrentState property let us know whether the object has been disconnected from .NET Remoting or not (the LeaseState.Expired value). Hence we need some kind of utility that scans all business objects from time to time and inform us when a business object is disconnected. I named it “BusinessObjectScavenger”.

Why is it a good idea to scan from a timer? Well, the entire .NET Remoting lifetime management is built on scanning connected to .NET Remoting MBR instances. Usually the time of disconnection can be calculated as MAX(CurrentLeaseTime, LeaseManagerPollTime) + TimeToBeingGarbageCollected.

This approach just adds an additional variable that is insensibly small: MAX(CurrentLeaseTime, LeaseManagerPollTime) + BusinessObjectScavengerPollTime + TimeToBeingGarbageCollected.

MAX(CurrentLeaseTime + LeaseManagerPollTime) usually equals to 2-5 minutes while BusinessObjectScavengerPollTime is 15 seconds by default. An additional 6% time error does not make any difference.

A business object class should implement the IDisposable interface and call its Dispose method in the destructor. In addition, it should register itself at BusinessObjectScavenger utility in the constructor. That’s all the expenses we incur in exchange for the predictable time of disposing:

public class BusinessObjectImplementation : MarshalByRefObject,
                                                          IDisposable
{
  // Constructs instance of the BusinessObjectImplementation class
  public BusinessObjectImplementation()
  {
    BusinessObjectScavenger.RegisterDisposedObject(this);

    // ...
  }

  // Destructor
  ~BusinessObjectImplementation()
  {
    this.Dispose();
  }

  private bool _disposed = false;

  // Releases important resources
  public void Dispose()
  {
    if (this._disposed)
      return ;
    this._disposed = true;

    // TODO: release resources here
  }
}

The Dispose method will be invoked after the specified timeout. By default, it is 15 seconds, however you can change the BusinessObjectScavenger.TimerFrequency property value before the first call of the BusinessObjectScavenger.RegisterDisposedObject method.

Scavenger does not hold on any references to the object being registered, so your object can be disposed by Garbage Collection. That's why we invoke the Dispose method in the destructor. During the disposing process, you should remove all references to this unused business object from all collections in order to let Garbage Collection take it.

Let’s review the BusinessObjectScavenger implementation and the sample in general. The server makes available an instance of the ObjectFactoryImplementation class at known URI. It’s a singleton that spawns business objects. Note that the ObjectFactoryImplementation class overrides the InitializeLifetimeService method and returns a null reference there.

static void Main(string[] args)
{
  // Set up .NET remoting
  ...  

  // bind the server
  RemotingServices.Marshal(new ObjectFactoryImplementation(),
      "ObjectFactory.rem");

  // and accept clients
  Console.ReadLine();
}

public class ObjectFactoryImplementation : MarshalByRefObject,
                                          IKnownBusinessObjectFactory
{
  // Returns a business object.
  public IKnownBusinessObject GetBusinessObject()
  {
    return new BusinessImplementation();
  }

  // This is to insure that when created as a Singleton, the first 
  // instance never dies,
  // regardless of the expired time.
  public override object InitializeLifetimeService()
  {
    return null;
  }
}

BusinessObjectScavenger collects weak references to given business objects. Its timer invokes the TimerCallback method where it goes through the list of all registered objects and checks the lease state of each object. If a lease state equals to LeaseState.Expired, the BusinessObjectScavenger invokes the Dispose method of the object and drops it out of the list. If an object has been collected by Garbage Collection, then it is dropped out of the list as well.

You should call the Dispose method in a separate thread if its execution time is expected to take a lengthy time period. Something like this:

public void Dispose()
{
  if (this._disposed)
    return ;
  this._disposed = true;

  GenuineThreadPool.QueueUserWorkItem (new WaitCallback
    (InternalDispose), null, true );
}

private void InternalDispose (Object stateInfo)
{
  // TODO: release resources here
}

The client implementation is pretty straightforward. It obtains a business object, registers a sponsor, makes two calls and then disables the sponsor. It's my own preference to disable the sponsor, not to unregister it from all MBR objects. As a result I never bother how many objects the sponsor holds. And when I need to obtain the next portion of MBR objects, I just create another sponsor.

IKnownBusinessObjectFactory iKnownBusinessObjectFactory =
       (IKnownBusinessObjectFactory) Activator.GetObject(typeof
                                     (IKnownBusinessObjectFactory),
       ConfigurationSettings.AppSettings["RemoteHostUri"] + 
                                     "/ObjectFactory.rem");
IKnownBusinessObject iKnownBusinessObject = 
       iKnownBusinessObjectFactory.GetBusinessObject();

// attach the sponsor
ClientSponsor clientSponsor = new ClientSponsor();
ILease lease = (ILease) RemotingServices.GetLifetimeService
       ((MarshalByRefObject) iKnownBusinessObject);
lease.Register(clientSponsor);

Console.WriteLine("Making the first call.");
iKnownBusinessObject.DoSomethings();

Console.WriteLine("Sleeping for 12 seconds.");
Thread.Sleep(TimeSpan.FromSeconds(12));

Console.WriteLine("Making the second call.");
iKnownBusinessObject.DoSomethings();

Console.WriteLine("Unregister our sponsor.");
clientSponsor.RenewalTime = TimeSpan.Zero;

I set very short lifetime values in the .config file, so if you comment out the registration of the sponsor, then the second call will fail.

Remember! It is an extremely good idea to detach or disable the client's sponsor at the proper time!

As a conclusion, I’d like to mention that this approach can be used for a pretty wide spectrum of different tasks. It is reliable until someone starts doing some lengthy processing in the Dispose method or forgets to detach a global sponsor from locally required business objects.

⚠️ **GitHub.com Fallback** ⚠️