4. Providing a Sponsor
As mentioned already, a
sponsor is a third party whom .NET consults when a lease expires, giving
that party an opportunity to renew the lease. The sponsor must
implement the ISponsor interface, defined as:
public interface ISponsor
{
TimeSpan Renewal(ILease lease);
}
The lease manager calls ISponsor's single method, Renewal( ),
when the lease expires, asking for new lease time. To add a sponsor to a
lease object, simply obtain the lease object by using GetLifetimeService( ) and call the ILease. Register( ) method:
public class MySponsor : MarshalByRefObject,ISponsor
{
public TimeSpan Renewal(ILease lease)
{
Debug.Assert(lease.CurrentState == LeaseState.Active);
//Renew lease by 5 minutes
return TimeSpan.FromMinutes(5);
}
}
ISponsor sponsor = new MySponsor( );
MyClass obj = new MyClass( );
//Register the sponsor
ILease lease = (ILease)RemotingServices.GetLifetimeService(obj);
lease.Register(sponsor);
If the sponsor doesn't want to renew the lease, it can return TimeSpan.Zero:
public TimeSpan Renewal(ILease lease)
{
Debug.Assert(lease.CurrentState == LeaseState.Active);
//Refuse to renew lease:
return TimeSpan.Zero;
}
However, it probably
makes more sense to unregister the sponsor instead. Because the sponsor
is called across an app domain boundary, the sponsor must be a remotable
object, meaning it must be marshaled either by value or by reference.
If you derive the sponsor from MarshalByRefObject,
the sponsor will reside on the client's side, and it can base its
decision about the renewal time on client-side events or properties that
it's monitoring. That raises an interesting question: if the lease
keeps the remote server object alive, and the sponsor keeps the lease
alive, who keeps the sponsor alive? The answer is that somebody on the
client side must keep a reference to the sponsor, typically as a class
member variable. Doing so also allows the client to remove the sponsor
from the lease when the client shuts down, by calling the ILease.Unregister( ) method (the client can also unregister the sponsor in its implementation of IDisposable.Dispose( )).
Unregistering sponsors improves overall performance, because the lease
manager doesn't spend time trying to reach sponsors that aren't
available.
If you mark only
the sponsor as serializable, when you register the sponsor it's
marshaled by value to the host's side and will reside there. This
eliminates the marshaling overhead of contacting the sponsor, but it
also disconnects the sponsor from the client. A marshaled-by-value
sponsor can only base its decisions about renewing leases on information
available on the host's side.
4.1. Sponsors and remoting
When the sponsor is a
marshaled-by-reference object, the client application must register a
port with its channels to allow the remote lease manager to call back to
the sponsor, and it must set the type filtering to Full. The client
generally doesn't care which port is used for the callbacks, so it can
register port number 0 to instruct .NET to automatically select an
available port. The channel, port number, and sponsor object location
are captured when the reference to the sponsor object is marshaled to
the remote host.
4.2. Client and leases
A single lease can
have multiple sponsors, and multiple clients can all share the same
sponsor. In addition, a single sponsor can be registered with multiple
remote leases. Typically, a client application will have one sponsor for
all its remote objects. As a result, a remote object will typically
have as many sponsors to its lease as it has distinct client
applications using it. When a client application shuts down, it
unregisters its sponsor from all the remote leases it sponsors.
5. Leasing and Remote Activation Modes
Server-activated single-call objects don't need leasing, because such objects are disconnected immediately after each call. This isn't the case for server-activated singleton objects and client-activated objects, though.
5.1. Leasing a singleton object
The singleton
design pattern semantics mandate that once it's created, the singleton
object lives forever. Thus, the default lease time of five minutes
doesn't make sense for singleton objects. If you don't change this
default and no client accesses the singleton object for more than five
minutes after it's created (or just two minutes after the first five
minutes), .NET deactivates the singleton. Future calls from the clients
are silently routed to a new singleton object. .NET supports infinite
lease time. When you design a singleton object, override InitializeLifetimeService( ) and return a null object as the new lease, indicating to .NET that this lease never expires:
public class MySingleton : MarshalByRefObject
{
public override object InitializeLifetimeService( )
{
return null;
}
}
Returning an infinite
lease relieves you of managing global lease defaults or dealing with
sponsors. In fact, you can use an infinite lease with any object
activation mode, but it makes sense only in the case of a singleton.
5.2. Leasing a client-activated object
Client-activated objects are the most affected by the leasing
mechanism. The only safe way to manage client-activated objects is to
use sponsors. All other options, such as setting global lease properties
or configuring individual objects' leases, are guesses or heuristics at
best. Ultimately, only the client knows when it no longer requires an
object. The sponsor should renew each object's lease with an amount of
time that on the one hand balances network traffic and load on the lease
manager, and on the other hand is granular enough to manage the
resources the object may hold. If the sponsor provides too short a
renewed lease the lease manager will have to query it frequently to
renew the lease, which may result in unnecessary traffic. If the
sponsored lease is too long, the lease manager may end up keeping the
remote object alive when the client no longer needs it. Every case is
unique, and when throughput and performance are a concern, you will have
to investigate and profile the system with various sponsorship renewal
times. I can, however, offer the following rule of thumb: in general,
the sponsorship time should be the same as the initial lease time. The
reason is that if the initial lease time is good enough for your
case—that is, it doesn't generate too much traffic and isn't too
coarse—it's likely to be just as suitable for the sponsors on subsequent
lease-extension requests.
Example 2
demonstrates a client using a client-activated object whose lifetime is
controlled by a sponsor. You can use this code as a template or a
starting point for your remote client-activated objects. The solution has three projects: the ServerAssembly class library, the RemoteServerHost EXE assembly, and the Client EXE assembly. The host registers the MyCAO
type as a client-activated object. Because the sponsor is provided to
the host in the form of a remote callback object, the host must set its
channel's type-filtering level to Full. The rest of the host is omitted
from Example 2. The ServerAssembly class library contains the MyCAO definition, so both the client and the host can access its metadata. The client assembly contains the MySponsor class, because there is no need to put the sponsor in a class library; all the host needs is the metadata for the ISponsor interface, which is already part of the .NET class libraries. The MySponsor implementation of Renewal( ) simply extends the lease by the initial lease time. The client configuration file designates the MyCAO
type as a remote client-activated object, and it registers the port 0
with the channel, as well as elevating type filtering to Full to allow
calls back to the sponsor. The client's class (ClientForm)
has as member variables both the remote client-activated object and the
sponsor. In its constructor, the client creates new instances of the
server and the sponsor and registers the sponsor with the lease of the
client-activated object. The client is a Windows Forms dialog, with a
single button. When clicked, the button simply calls the
client-activated object. When the user closes the dialog, the client
unregisters the sponsors.
Example 2. Sponsoring a client-activated object
///////////// RemoteServerHost.exe.config : the host configuration file ////////
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.runtime.remoting>
<application>
<service>
<activated type="RemoteServer.MyCAO,ServerAssembly"/>
</service>
<channels>
<channel ref="tcp" port="8005">
<serverProviders>
<formatter ref="soap" typeFilterLevel="Full"/>
<formatter ref="binary" typeFilterLevel="Full"/>
</serverProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>
///////////////////// ServerAssembly class library ////////////////////////////
namespace RemoteServer
{
public class MyCAO : MarshalByRefObject
{
int m_Counter = 0;
public void Count( )
{
m_Counter++;
string appName = AppDomain.CurrentDomain.FriendlyName;
MessageBox.Show("Counter value is " + m_Counter,appName);
}
}
}
//////////////// Client.exe.config: the client configuration file //////////////
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.runtime.remoting>
<application>
<client url="tcp://localhost:8005">
<activated type="RemoteServer.MyCAO,ServerAssembly"/>
</client>
<channels>
<channel ref="tcp" port="0">
<serverProviders>
<formatter ref="soap" typeFilterLevel="Full"/>
<formatter ref="binary" typeFilterLevel="Full"/>
</serverProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>
/////////////////////////// Client EXE assembly ////////////////////////////////
using System.Runtime.Remoting.Lifetime;
public class MySponsor : MarshalByRefObject,ISponsor
{
public TimeSpan Renewal(ILease lease)
{
Debug.Assert(lease.CurrentState == LeaseState.Active);
return lease.InitialLeaseTime;
}
}
using RemoteServer;
partial class ClientForm : Form
{
Button m_CallButton;
ISponsor m_Sponsor;
MyCAO m_MyCAO;
public ClientForm( )
{
InitializeComponent( );
m_Sponsor = new MySponsor( );
m_MyCAO = new MyCAO( );
//Register the sponsor
ILease lease = (ILease)RemotingServices.GetLifetimeService(m_MyCAO);
lease.Register(m_Sponsor);
}
void InitializeComponent( )
{...}
static void Main( )
{
RemotingConfigurationEx.Configure( );
Application.Run(new ClientForm( ));
}
void OnCall(object sender,EventArgs e)
{
m_MyCAO.Count( );
}
void OnClosed(object sender,EventArgs e)
{
//Unegister the sponsor
ILease lease = (ILease)RemotingServices.GetLifetimeService(m_MyCAO);
lease.Unregister(m_Sponsor);
}
}
|