Cook Computing

Leasing and Deterministic Resource Management

May 27, 2003 Written by Charles Cook

As has been discussed many times, deterministic finalization is not possible in managed code. But another form of deterministic mangement of resources is possible through the use of leasing, using the fact that an object is disconnected when a lease times out. We can use this to ensure that the resources held by an object are released either within a specified time period from the creation of the lease if no calls have been made to the object, or a specified timeout period after the most recent call to the object. When the lease times out and the object is disconnected, we call the object's IDispose method if it implements the IDisposable interface used to indicate that an object requires explicit cleanup.

To use leasing the object must be in a different AppDomain to its clients. If the client is in a separate process then this is obviously the case, whereas in the in-process case we need to create a separate AppDomain in which we instantiate the object.

Trapping the disconnection event is achieved by registering an instance of the ITrackingHandler in the AppDomain where we are creating the object:


interface ITrackingHandler
{
  public void MarshaledObject(object obj, ObjRef or);
  public void UnmarshaledObject(object obj, ObjRef or);
  public void DisconnectedObject(object obj);
}

The DisconnectedObject method of the handler is called by the Remoting infrastructure when the connection between the client and our object is broken. Disconnection can happen for various reasons such as the client process terminating, the client object being garbage collected, or, of most interest here, when the client's lease on the object expires.

Building on the sample code from the recent post on configuring leases, the factory class is extended to implement ITrackingHandler:


public class ClassFactory : MarshalByRefObject, ITrackingHandler
{
  public ClassFactory()
  {
    TrackingServices.RegisterTrackingHandler(this) ;
    LifetimeServices.LeaseManagerPollTime = TimeSpan.FromSeconds(1);
  }

  public object Create()
  {
    TestClass obj = new TestClass();
    ILease lease = (ILease)obj.InitializeLifetimeService();
    if (lease != null)
    {
      lease.InitialLeaseTime = TimeSpan.FromSeconds(5);
      lease.SponsorshipTimeout = TimeSpan.FromSeconds(5);
      lease.RenewOnCallTime = TimeSpan.FromSeconds(5);
    }
    return obj;
  }

  // ITrackingHandler methods
  public void MarshaledObject(object obj, ObjRef or)
  {
  }
   
  public void UnmarshaledObject(object obj, ObjRef or)
  {
  }
    
  public void DisconnectedObject(object obj)
  {
    if (obj is BaseClass && obj is IDisposable)
      (obj as IDisposable).Dispose();
  }
}

The ClassFactory constructor registers the handler and also sets the LeaseManagerPollTime to configure the time interval between each activation of the lease manager when it checks expired leases. The default interval is 10 seconds but a smaller timespan is used to here to make when disconnection happens more obvious when running the sample code.

We don't necessarily want IDispose to be called on every object which is disconnected in the current AppDomain so we restrict this to classes which derive from the sample BaseClass which is used as the base class for all classes on which we want to manage resources.


public class BaseClass : MarshalByRefObject
{
  ILease _lease;

  public override Object InitializeLifetimeService()
  {
    if (_lease == null)
      _lease = (ILease)base.InitializeLifetimeService();
    return _lease;
  }
}

Note that BaseClass must derive from MarshalByRefObject. The override of InitializeLifetimeService allows the factory class to set the lease (as described in the recent post). We then derive from this class, implementing IDisposable if cleanup is required:

public class TestClass : BaseClass, IDisposable
{
  public string GetAppDomainInfo()
  {
    return "AppDomain = " + AppDomain.CurrentDomain.FriendlyName;
  }

  public void Dispose()
  {
    // free up resources held by object
    Console.WriteLine("Dispose called");
  }
}

Finally, we need some code to drive all of this:

public class App
{
  public static int Main()
  {
    AppDomain ad = AppDomain.CreateDomain("ResourceDomain", 
        null, null);
    ObjectHandle oh = ad.CreateInstance("TestLeaseTimeout", 
        "ClassFactory");
    ClassFactory factory = (ClassFactory)(oh.Unwrap());

    TestClass tc = (TestClass)factory.Create();
    string info = tc.GetAppDomainInfo();
    Console.WriteLine("AppDomain info: {0}", info);
    System.Threading.Thread.Sleep(20000);
    return 0;
  }
}

We create a child AppDomain and instantiate the factory class within it. We create an instance of TestClass, make a call on it, and sleep to allow for the object to be disconnected and its Dispose method called.

This technique is a coarse-grained and fairly expensive way of managing resources but it can be useful. The more obvious examples that spring to mind are where a remoted client acquires objects on the server and the server-side objects acquire valuable resources. If the client fails to dispose of the objects then we want to reclaim the resources within a fixed period of time rather than relying on garbage collection.