.NET manages the lifecycle of
objects using garbage collection. .NET keeps track of memory allocation
and objects accessed by all the clients in the app domain. When an
object becomes unreachable by its clients, the garbage collector
eventually collects it. If the objects are in the same app domain as the
clients, garbage collection functions fine. In fact, even in the case
of a client in one app domain accessing an object in a different app
domain in the same process, garbage collection still works, because all
app domains in the same process share the same managed heap. In the case
of remote objects accessed across processes and machines, however, the
strategy breaks down because the object may not have any local clients.
In this case, if garbage collection were to take place, the garbage
collector would not find any references to the object and would deem it
garbage, even though there are remote clients (on other machines, or
even in separate processes on the same machine) who wish to use the
object. The rest of this section addresses this challenge.
In the following discussion, a
"remote object" is an object in a different process. The core piece of
the .NET remoting architecture designed to address this problem is
called leasing and sponsorship. The idea behind leasing is simple: each server object accessed by remote clients is associated with a lease object. The lease object
literally gives the server object a lease on life. When a client
creates a remote server object (that is, actually creates it, rather
than connects to an existing instance), .NET creates a lease object and
associates it with the server object. A special entity in .NET remoting
called the lease
manager
keeps track of the server objects and their lease objects. Each lease
object has an initial lease time. The clock starts ticking as soon as
the first reference to the server object is marshaled across the app
domain boundary, and the lease time is decremented as time goes by. As
long as the lease time doesn't expire, .NET considers the server object
as being used by its clients. The lease manager
keeps a reference to the server object, which prevents the server
object from being collected in case garbage collection is triggered.
When the lease expires, .NET assumes that the server object has no
remaining remote clients. .NET then disconnects the server object from
the remoting infrastructure. The server object becomes a candidate for
garbage collection and is eventually destroyed. After the object is
disconnected, any client attempt to access it results in an exception of
type RemotingException, letting the
client know the object has been disconnected. This may appear strange at
first, because the object may very well still be alive. .NET behaves
this way because otherwise, the client's interaction with the remote
object will be nondeterministic. If .NET allowed remote clients to
access objects past their lease time, it would work some of the time but
would fail in those cases in which garbage collection had already taken
place.
If the remote object is disconnected because the lease has expired, the client can't call any method on it, including IDisposable.Dispose( ).
This may have serious scalability consequences. When the object
contains expensive resources, make sure to use single-call objects—these
don't require leasing, and .NET calls IDisposable.Dispose( ) on them automatically. |
|
But what about those cases
where there are still some remote clients who would like to keep the
server object alive after its lease expires? The smart thing to do is to
contact such clients and ask them to extend the lease, or, in .NET
terminology, to sponsor the lease. Clients that wish .NET to contact them when a server object's lease expires need to provide .NET with a special sponsor object.
When the time comes, .NET will contact the sponsor object, giving it a
chance to extend the lease. This interaction is illustrated in Figure 1.
The sponsor can extend
the lease or refuse to do so. A given server object can have multiple
sponsors associated with its lease. The lease manager keeps a list of
all the sponsors associated with each lease, and when the lease expires
the lease manager starts traversing the sponsor list, looking for a
sponsor willing to extend the lease. If such a sponsor is found, the
lease manager extends the lease. Otherwise, the lease manager
disconnects the server object.
1. Lease Properties
Every lease has a number of properties associated with it. These properties control the manner in which the lease manager
interacts with the remote object's lease. .NET assigns some global
default values to these properties, but you can instruct .NET to use
other default values. You can even override the default lease properties for individual objects. The expired
time is the time that has expired since the beginning of the lease. A lease has a lease
time property. By default, if you don't configure it differently, each lease's lease
time
property is initially set to five minutes. .NET could simply disconnect
the object when the expired time is equal to the lease time, but what
should it do if clients continue to call the object? This clearly
indicates that the object is still useful. Every lease has a renew
on
call
time
property; if the lease is about to expire, .NET automatically extends
the lease on every call by the value set in the call time renewal
property. The default call time renewal is two minutes. The current
lease
time value
(the time the object has to live unless the lease is extended) is a
product of the remaining lease time and the renew on call time,
according to this formula:
current lease time = MAX(lease time—expired time,renew on call time)
If the renew on call time
value is less than the lease time minus the expired time, it will have
no effect. The renew on call time has an effect only if it is greater
than the lease time minus the expired time. In that case, the expired
time property is reset, and the lease time is set to the renew on call
time. The result is that even if an object is very busy, its lease time
doesn't grow in proportion to the amount of traffic it has. Even if the
object has a spike in load, after some quiet time, its lease will
expire.
1.1. Lease manager properties
There are two properties
pertaining to the lease manager itself. Obviously, the lease manager
needs to monitor the leases of all remote server objects in its app
domain. The question is, how often should the lease manager examine the
leases? The lease manager's poll
time
property governs the frequency with which the lease manager polls the
leases. The default poll time is set to 10 seconds. The other lease
manager property has to do with sponsors. The lease sponsors can reside
on remote machines, or it may take them a long time to reach a decision
on the lease's fate. The lease manager's sponsorship
timeout
property controls how long the lease manager should wait for a reply
from a sponsor. The sponsorship timeout is required to handle network
failures, or even the case of a sponsor machine being down. If the lease
manager tries to reach a sponsor and the sponsor doesn't reply within
the sponsorship timeout period, the lease manager removes that sponsor
from the sponsor list associated with the lease.
1.2. Configuring global default properties
If you don't like the default values of the various lease and lease manager properties,
you can provide your own. You can do so both programmatically and
administratively, using the application configuration file. To configure
global defaults programmatically, use the static properties of the LifetimeServices class, defined in the System.Runtime.Remoting.Lifetime namespace:
public sealed class LifetimeServices
{
public static TimeSpan LeaseManagerPollTime { get; set; }
public static TimeSpan LeaseTime { get; set; }
public static TimeSpan RenewOnCallTime { get; set; }
public static TimeSpan SponsorshipTimeout { get; set; }
}
You typically use these properties in the Main( ) method of your host:
static void Main( )
{
LifetimeServices.LeaseTime = TimeSpan.FromMinutes(10);
LifetimeServices.RenewOnCallTime = TimeSpan.FromMinutes(15);
/* Register types or load configuration file */
}
Note that you must
set the global leasing defaults before you register types
(programmatically or using the configuration file). The host can start
servicing remote calls immediately after registration, and if you
haven't already set the new defaults, these initial calls will not use
them.
To provide the new global default values in the host configuration file, use the <lifetime> element:
<configuration>
<system.runtime.remoting>
<application>
<lifetime
leaseTime = "10M"
sponsorshipTimeOut = "1M"
renewOnCallTime = "15M"
LeaseManagePollTime = "8s"
/>
</application>
</system.runtime.remoting>
</configuration>
2. Configuring a Lease
Every lease object implements the ILease interface, defined in the System.Runtime.Remoting.Lifetime namespace:
public interface ILease
{
TimeSpan CurrentLeaseTime {get;}
LeaseState CurrentState {get;}
TimeSpan InitialLeaseTime {get;set;}
TimeSpan RenewOnCallTime {get;set;}
TimeSpan SponsorshipTimeout {get;set;}
void Register(ISponsor obj);
void Register(ISponsor obj,TimeSpan renewalTime);
TimeSpan Renew(TimeSpan renewalTime);
void Unregister(ISponsor obj);
}
The ILease
interface allows you to control and configure the lease properties for
an individual object, as well as to manage sponsors for that lease. Both
the object and its clients can obtain the ILease interface. An individual lease can be in one of a number of states; the most important are initial, active, and expired. You can obtain the state of the lease by accessing the CurrentState read-only property of the ILease interface. CurrentState is of the enum type LeaseState:
public enum LeaseState
{
Active,
Expired,
Initial,
Null,
Renewing
}
A remote class can
provide its own values to the lease's properties, giving the object
control over its lifetime. To do so, override the InitializeLifetimeService( ) method defined in MarshalByRefObject and return a lease object. .NET calls InitializeLifetimeService( ) immediately after the remote object's constructor, but before a reference to the object is marshaled back to the client. InitializeLifetimeService( ) is never called if a local client creates the object. Although you can return from InitializeLifetimeService( ) any object that implements the ILease
interface, in practice you need to obtain the lease already associated
with your object and modify its properties. You do that by calling your
base class's InitializeLifetimeService( ) method and modifying the lease properties, as shown in Example 1. You can set the lease properties only if the lease is in the LeaseState.Initial state, and this is asserted in the example.
Example 1. Providing new lease properties for an object
public class MyServer : MarshalByRefObject
{
public override object InitializeLifetimeService( )
{
ILease lease = (ILease)base.InitializeLifetimeService( );
Debug.Assert(lease.CurrentState == LeaseState.Initial);
//Set lease properties
lease.InitialLeaseTime = TimeSpan.FromMinutes(30);
lease.RenewOnCallTime = TimeSpan.FromMinutes(10);
lease.SponsorshipTimeout = TimeSpan.FromMinutes(2);
return lease;
}
}
|
3. Renewing a Lease
Both the object and its clients can extend the lease explicitly by obtaining the object's lease and calling the ILease.Renew( ) method, providing a new lease time. Renewing a lease explicitly affects the current lease time according to this formula:
current lease time = MAX(lease time—expired time,renewal time)
This means that the
renewal time will have an effect only if the renewal time is greater
than the lease time minus the expired time. In that case, the expired
time is reset, and the lease time becomes the renewal time.
Consequently, if different clients all try to explicitly renew a lease,
the lease will not grow to the value of their combined renewal sum; this
ensures that the object remains connected only when clients require it.
Both the client and the object obtain the lease associated with the
object using the static method GetLifetimeService( ) of the RemotingServices class:
public static object GetLifetimeService(MarshalByRefObject obj);
You can renew a lease only if it's in the LeaseState.Active state. For example, here is how a client renews a lease:
MyClass obj;
obj = new MyClass( );
ILease lease = (ILease)RemotingServices.GetLifetimeService(obj);
Debug.Assert(lease.CurrentState == LeaseState.Active);
lease.Renew(TimeSpan.FromMinutes(30));
If the object wants to renew its own lease, it simply calls GetLifetimeService( ), providing itself as the parameter:
public class MyServer : MarshalByRefObject
{
public void SomeMethod( )
{
ILease lease = (ILease)RemotingServices.GetLifetimeService(this);
Debug.Assert(lease.CurrentState == LeaseState.Active);
lease.Renew(TimeSpan.FromMinutes(30));
//Do some work
}
}