Chapter 6: In-Depth .NET Remoting

AS YOU'VE ALREADY SEEN in the previous chapters, developers of distributed applications using .NET Remoting have to consider several fundamental differences from other remoting techniques and, of course, from the development of local applications. One of the major issues you face in any distributed object framework is the decision of how to manage an object's lifetime. Generally you have two possibilities: using distributed reference counting/garbage collection or using time-to-live counters associated with each object.

Managing an Object's Lifetime

Both CORBA and DCOM have employed distributed reference counting. With DCOM, for example, the server's objects keep counters of referrers that rely on the AddRef() and Release() methods in the same way as it's done for common COM objects. Unfortunately this has some serious drawbacks: each call to increase or decrease the reference counter has to travel to the remote object without any "real" application benefit (that is, the remote call does not do any "real" work). In DCOM, the clients will also ping the server at certain intervals to signal that they are still alive.

Both pinging and the calls to change the reference counter result in an increased network load, and the former will very likely not work with some firewalls or proxies that only allow stateless HTTP connections to pass through.

Because of those implications, Java RMI introduced a lease-based lifetime service that bears a close resemblance to what you can see in .NET Remoting today. The lease-based concept essentially assigns a time-to-live (TTL) count to each object that's created at the server. A LeaseManager then polls all server-side objects at certain intervals and decrements this TTL. As soon as this time reaches zero, the object is marked as timed out and will be marked for garbage collection. Additionally, for each method call placed on the remote object, the TTL is incremented again to ensure that objects currently in use will not time out.

In reality, though, there are applications in which objects may exist that are not used all the time. A pure TTL-based approach would time-out these objects too soon. Because of this, the .NET Remoting framework also supports a concept called sponsorship. For each object, one or more sponsors might be registered. Upon reaching zero TTL, the LeaseManager contacts each sponsor and asks if it wants to increase the object's lifetime. Only when none of them responds positively in a given time is the object marked for garbage collection.

A sponsor itself is a MarshalByRefObject as well. It can therefore be located on the client, the server, or any other machine that is reachable via .NET Remoting.

Understanding Leases

A lease holds the time-to-live information for a given object. It is therefore directly associated with a certain MarshalByRefObject's instance. At the creation of a lease, the following information is set (all of the following are of type TimeSpan):


PROPERTY

DEFAULT

DESCRIPTION

InitialLeaseTime

5 minutes

The initial TTL after an object's creation.

RenewOnCallTime

2 minutes

The grace time for a method call that is placed on the object. Mind, though, that these times are not additive—for instance, calling a method a thousand times will not result in a TTL of 2,000 minutes, but one of 2 minutes.

SponsorShipTimeout

2 minutes

When sponsors are registered for this lease, they will be contacted upon expiration of the TTL. They then can contact the LeaseManager to request additional lease time for the sponsored object. When no sponsor reacts during the time defined by this property, the lease will expire and the object will be garbage collected.


Both the ILease interface and the Lease class that provides the standard implementation are located in System.Runtime.Remoting.Lifetime. Whenever aMarshalByRefObject is instantiated (either as a CAO or as a SAO—even when using Singleton mode), the framework calls the object's InitializeLifetimeService() method, which will return an ILease object. In the default implementation (that is, when you don't override this method), the framework calls LifetimeServices.GetLeaseInitial(), which returns a Lease object containing the defaults shown in the preceding table.

Tip 

Whenever I mention some class for which you don't know the containing namespace, you can use WinCV.exe, which is in the Framework SDK, to locate the class and get some information about its public interface.

The Role of the LeaseManager

The LeaseManager runs in the background of each server-side application and checks all remoted objects for their TTL. It uses a timer and a delegate that calls its LeaseTimeAnalyzer() method at certain intervals.

The initial value for this interval is set to 10 seconds. You can change this interval by using either the following line of code:

 LifetimeServices.LeaseManagerPollTime = TimeSpan.FromSeconds(1); 

or, when using configuration files, you can add the following settings to it:

 <configuration>    <system.runtime.remoting>       <application>          <lifetime leaseManagerPollTime="1s" />       </application>    </system.runtime.remoting> </configuration> 

You may specify different time units for the leaseManagerPollTime attribute. Valid units are D for Days, H for hours, M for minutes, S for seconds, and MS for milliseconds. When nothing is specified, the system will default to S; combinations such as "1H5M" are not supported.

Changing the Default Lease Time

You can easily change the default TTL for all objects in a given server-side appdomain in two ways. First, you can use the following code fragment to alter the application-wide initial lease times:

 LifetimeServices.LeaseTime = System.TimeSpan.FromMinutes(10); LifetimeServices.RenewOnCallTime = System.TimeSpan.FromMinutes(5); 

As the preferred alternative, you can add the following sections to your configuration files:

 <configuration>    <system.runtime.remoting>       <application>          <lifetime             leaseTimeout="10M"             renewOnCallTime=”5M”          />       </application>    </system.runtime.remoting> </configuration> 

However, you have to be aware of the fact that this change affects each and every remote object that is published by this server. Increasing the TTL therefore can have negative effects toward the memory and resource utilization of your application, whereas decreasing it can lead to objects being prematurely destroyed.

Caution 

Whenever a client places a method call to a remote object with an expired TTL, an exception will be thrown.

Changing the Lease Time on a Per-Class Basis

For certain MarshalByRefObjects (especially Singleton-mode services or objects published by RemotingServices.Marshal()), it is desirable to have either an "unlimited" TTL or a different lease time from that of other objects on the same server.

You can implement this functionality by overriding MarshalByRefObject's InitializeLifetimeService(). This method is defined to return an object, but later uses in the framework will cast this object to Lease, so make sure not to return anything else. For example, to provide, a Singleton with unlimited lifetime, implement the following:

 class InifinitelyLivingSingleton: MarshalByRefObject {    public override object InitializeLifetimeService()    {       return null;    }     // ... } 

To set a custom lifetime different from "infinity," you can call base.InitializeLifetimeService() to acquire the reference to the standard ILease object and set the corresponding values afterwards.

 class LongerLivingSingleton: MarshalByRefObject {    public override object InitializeLifetimeService()    {      ILease tmp = (ILease) base.InitializeLifetimeService();      if (tmp.CurrentState == LeaseState.Initial)      {          tmp.InitialLeaseTime = TimeSpan.FromSeconds(5);          tmp.RenewOnCallTime = TimeSpan.FromSeconds(1);       }       return tmp;    } } 

Examining a Basic Lifetime Example

In the following example, I show you how to implement the different changes in an object's lifetime in one application. The server will therefore export three MarshalByRefObjects as Singletons: DefaultLifeTimeSingleton, which will use the "base" lifetime set by a configuration file; LongerLivingSingleton, which will over-ride InitializeLifetimeService() to return a different lease time; and finally InfinitelyLivingSingleton, which will just return null from InitializeLifetimeServices().

As you can see in the following configuration file, I change the default lifetime to a considerable lower value so that you can observe the effects without having to wait five minutes until the objects time out:

 <configuration>   <system.runtime.remoting>     <application>       <channels>         <channel ref="http" port="1234" />       </channels>       <lifetime          leaseTime="10MS"          renewOnCallTime="10MS"          leaseManagerPollTime = "5MS"       />       <service>         <wellknown mode="Singleton"                        type="Server.DefaultLifeTimeSingleton, Server"                        objectUri="DefaultLifeTimeSingleton.soap" />         <wellknown mode="Singleton"                        type="Server.LongerLivingSingleton, Server"                        objectUri="LongerLivingSingleton.soap" />         <wellknown mode="Singleton"                        type="Server.InfinitelyLivingSingleton, Server"                        objectUri="InfinitelyLivingSingleton.soap" />       </service>     </application>   </system.runtime.remoting> </configuration> 

In the server-side implementation shown in Listing 6-1, I just include some Console.WriteLine() statements so that you can see when new objects are created by the framework.

Listing 6-1: Implementation Showing the Effects of Different Lifetime Settings

start example
 using System; using System.Runtime.Remoting.Lifetime; using System.Runtime.Remoting; namespace Server {    class DefaultLifeTimeSingleton: MarshalByRefObject    {       public DefaultLifeTimeSingleton()       {          Console.WriteLine("DefaultLifeTimeSingleton.CTOR called");       }       public void doSomething()       {          Console.WriteLine("DefaultLifeTimeSingleton.doSomething called");       }    }    class LongerLivingSingleton: MarshalByRefObject    {      public override object InitializeLifetimeService()      {         ILease tmp = (ILease) base.InitializeLifetimeService();         if (tmp.CurrentState == LeaseState.Initial)         {            tmp.InitialLeaseTime = TimeSpan.FromSeconds(5);            tmp.RenewOnCallTime = TimeSpan.FromSeconds(1);         }         return tmp;      }       public LongerLivingSingleton()        {          Console.WriteLine("LongerLivingSingleton.CTOR called");       }       public void doSomething()       {          Console.WriteLine("LongerLivingSingleton.doSomething called");       }    }    class InfinitelyLivingSingleton: MarshalByRefObject    {      public override object InitializeLifetimeService()      {         return null;      }       public InfinitelyLivingSingleton()       {          Console.WriteLine("InfinitelyLivingSingleton.CTOR called");       }       public void doSomething()       {          Console.WriteLine("InfinitelyLivingSingleton.doSomething called");       }    }    class ServerStartup    {       public static void Main(String[] args)       {          RemotingConfiguration.Configure("Server.exe.config");          Console.WriteLine("Press <enter> to exit");          Console.ReadLine();       }    } } 
end example

To develop the client, you can use soapsuds -ia:server -nowp -oa:generated_meta.dll to generate a metadata assembly that will be referenced by the client application. In the example shown in Listing 6-2, the different Singletons will be called several times with varying delays.

Listing 6-2: The Client Calling the Various SAOs with Different Delays

start example
 using System; using System.Runtime.Remoting; using System.Threading; using Server; // from generated_meta.dll namespace Client {    class Client    {       static void Main(string[] args)       {          String filename = "client.exe.config";          RemotingConfiguration.Configure(filename);          DefaultLifeTimeSingleton def = new DefaultLifeTimeSingleton();          LongerLivingSingleton lng = new LongerLivingSingleton();          InfinitelyLivingSingleton inf = new InfinitelyLivingSingleton();          /*** FIRST BLOCK ***/          Console.WriteLine("Calling DefaultLifeTimeSingleton");          def.doSomething();          Console.WriteLine("Sleeping 100 msecs");          Thread.Sleep(100);          Console.WriteLine("Calling DefaultLifeTimeSingleton (will be new)");          def.doSomething(); // this will be a new instance          /*** SECOND BLOCK ***/          Console.WriteLine("Calling LongerLivingSingleton");          lng.doSomething();          Console.WriteLine("Sleeping 100 msecs");          Thread.Sleep(100);          Console.WriteLine("Calling LongerLivingSingleton (will be same)");          lng.doSomething(); // this will be the same instance          Console.WriteLine("Sleeping 6 seconds");          Thread.Sleep(6000);          Console.WriteLine("Calling LongerLivingSingleton (will be new)");          lng.doSomething(); // this will be a new same instance          /*** THIRD BLOCK ***/          Console.WriteLine("Calling InfinitelyLivingSingleton");          inf.doSomething();          Console.WriteLine("Sleeping 100 msecs");          Thread.Sleep(100);          Console.WriteLine("Calling InfinitelyLivingSingleton (will be same)");          inf.doSomething(); // this will be the same instance          Console.WriteLine("Sleeping 6 seconds");          Thread.Sleep(6000);          Console.WriteLine("Calling InfinitelyLivingSingleton (will be same)");          inf.doSomething(); // this will be a new same instance          Console.ReadLine();       }    } } 
end example

In the first block, the client calls DefaultLifetimeSingleton twice. As the delay between both calls is 100 milliseconds and the object's lifetime is 10 milliseconds, a new instance of the SAO will be created.

The second block calls LongerLivingSingleton three times. Because of the increased lifetime of 5 seconds, the first two calls will be handled by the same instance. A new object will be created for the third call, which takes place after a 6-second delay.

In the last block, the client executes methods on InfinitelyLivingSingleton. Regardless of which delay is used here, the client will always talk to the same instance due to the fact that InitializeLifetimeService() returns null, which provides infinite TTL. Figures 6-1 and 6-2 prove these points.

click to expand
Figure 6-1: Client-side output when dealing with different lifetimes

click to expand
Figure 6-2: Server-side output when dealing with different lifetimes

Extending the Sample

The .NET Remoting Framework only allows the default lifetime to be changed for all objects, which might invite you to hard code changes for class-specific TTLs. The problem here is that you might not necessarily know about each possible deployment scenario when developing your server-side components, so non-default lifetimes should in reality be customizable by configuration files as well.

You can therefore change your applications to not directly derive from MarshalByRefObjects, but instead from an enhanced subtype that will check the application's configuration file to read and set changed lifetime values.

Tip 

I think it's good practice to use an extended form of MarshalByRefObject for your applications, as you might not always know which kind of common functionality you'll want to implement later.

The ExtendedMBRObject, which is shown in Listing 6-3, will override InitializeLifetimeService() and check the appSetting entries in the configuration file for nondefault lifetime information on a class-by-class basis.

Listing 6-3: This Class Is the Base for the Following Examples

start example
 using System; using System.Configuration; using System.Runtime.Remoting.Lifetime; namespace Server {    public class ExtendedMBRObject: MarshalByRefObject    {       public override object InitializeLifetimeService()       {          String myName = this.GetType().FullName;         String lifetime =            ConfigurationSettings.AppSettings[myName + "_Lifetime"];         String renewoncall =            ConfigurationSettings.AppSettings[myName + "_RenewOnCallTime"];         String sponsorshiptimeout =            ConfigurationSettings.AppSettings[myName + "_SponsorShipTimeout"];         if (lifetime == "infinity")          {             return null;          }          else          {             ILease tmp = (ILease) base.InitializeLifetimeService();             if (tmp.CurrentState == LeaseState.Initial)             {                if (lifetime != null)                {                 tmp.InitialLeaseTime =                    TimeSpan.FromMilliseconds(Double.Parse(lifetime));                }                if (renewoncall != null)                {                 tmp.RenewOnCallTime =                    TimeSpan.FromMilliseconds(Double.Parse(renewoncall));                }                if (sponsorshiptimeout != null)                {                 tmp.SponsorshipTimeout =                    TimeSpan.FromMilliseconds(Double.Parse(sponsorshiptimeout));                }             }             return tmp;          }       }    } } 
end example

In the following example, all server-side objects are changed to inherit from ExtendedMBRObject instead of MarshalByRefObject. You'll also have to remove the calls to InitializeLifetimeService(), as this is currently done by the super-class. You can now add the following properties for each class (all of them are optional) to the server-side configuration file:


PROPERTY

DESCRIPTION

<Typename>_Lifetime

Initial TTL in milliseconds, or "infinity"

<Typename>_RenewOnCallTime

Time to add to a method call in milliseconds

<Typename>_SponsorshipTimeout

Maximum time to react for sponsor objects


To make this example behave the same as the previous one, you can use the following server-side configuration file:

 <configuration>   <system.runtime.remoting>     <application>       <channels>         <channel ref="http" port="5555" />       </channels>       <lifetime             leaseTime="10MS"             renewOnCallTime="10MS"             leaseManagerPollTime = "5MS"       />       <service>         <wellknown mode="Singleton"                     type="Server.DefaultLifeTimeSingleton, Server"                     objectUri="DefaultLifeTimeSingleton.soap" />         <wellknown mode="Singleton"                     type="Server.LongerLivingSingleton, Server"                     objectUri="LongerLivingSingleton.soap" />         <wellknown mode="Singleton"                     type="Server.InfinitelyLivingSingleton, Server"                     objectUri="InfinitelyLivingSingleton.soap" />       </service>     </application>   </system.runtime.remoting>   <appSettings>     <add key="Server.LongerLivingSingleton_LifeTime" value="5000" />     <add key="Server.LongerLivingSingleton_RenewOnCallTime" value="1000" />     <add key="Server.InfinitelyLivingSingleton_LifeTime" value="infinity" />   </appSettings> </configuration> 

When the new server is started (the client doesn't need any changes for this), you'll see the server-side output shown in Figure 6-3, which demonstrates that the changes were successful and the newly created server objects really read their lifetime settings from the configuration file.

click to expand
Figure 6-3: The configured server behaves as expected.

Working with Sponsors

Now that I've covered the primary aspects of lifetime management in the .NET Remoting Framework, I next show you the probably most confusing (but also most powerful) part of it: the sponsorship concept.

Whenever a remote object is created, a sponsor can be registered with it. This sponsor is contacted by the LeaseManager as soon as the object's time to live is about to expire. It then has the option to return a TimeSpan, which will be the new TTL for the remote object. When a sponsor doesn't want to extend an object's lifetime, it can simply return TimeSpan.Zero.

The sponsor object itself is a MarshalByRefObject that has to implement the interface ISponsor. The only other requisite for a sponsor is to be reachable by the .NET Remoting Framework. It can therefore be located either on the remoting server itself, on another server application, or on the client application.

Caution 

Beware, though, that when using client-side sponsors, the server has to be able to contact the client directly (the client becomes a server itself in this case, as it's hosting the sponsor object).When you are dealing with clients behind firewalls, this approach will not work.

Implementing the ISponsor Interface

Sponsors have to implement the ISponsor interface, which is defined in System.Runtime.Remoting.Lifetime. It contains just one method, which will be called by the LeaseManager upon expiration of a lease's time to live.

 public interface ISponsor {        TimeSpan Renewal(System.Runtime.Remoting.Lifetime.ILease lease) } 

The sponsor has to return a TimeSpan that specifies the new TTL for the object. If the sponsor decides not to increase the LeaseTime, it can return TimeSpan.Zero. A basic sponsor can look like the following:

 public class MySponsor: MarshalByRefObject, ISponsor {    private bool NeedsRenewal()    {       // check some internal conditions       return true;    }    public TimeSpan Renewal(System.Runtime.Remoting.Lifetime.ILease lease)    {       if (NeedsRenewal())       {          return TimeSpan.FromMinutes(5);       }       else       {          return TimeSpan.Zero;       }    } } 

Using Client-Side Sponsors

When using client-side sponsors, you are basically mimicking the DCOM behavior of pinging, although you have more control over the process here. After acquiring the reference to a remote object (you'll do this mostly for CAOs, as for SAOs the lifetime should normally be managed only by the server), you contact its lifetime service and register the sponsor with it.

You can get an object's LifetimeService, which will be an ILease object, using the following line of code:

 ILease lease = (ILease) obj.GetLifetimeService(); 

The ILease interface supports a Register() method to add another sponsor for the underlying object. When you want to hold a reference to an object for an unspecified amount of time (maybe while your client application is waiting for some user input), you can register a client-side sponsor with it and increase the TTL on demand.

Calling an Expired Object's Method

In the example shown in Listing 6-4, you see the result of calling an expired object's method. This happens because the server-side lifetime is set to one second, whereas the client uses a five-second delay between two calls to the CAO. The output is shown in Figure 6-4.

Listing 6-4: Catching the Exception When Calling an Expired Object

start example
 using System; using System.Runtime.Remoting; using System.Threading; using Server; // from generated_meta.dll namespace Client {    class Client    {       static void Main(string[] args)       {          String filename = "client.exe.config";          RemotingConfiguration.Configure(filename);          SomeCAO cao = new SomeCAO();          try          {             Console.WriteLine("{0} CLIENT: Calling doSomething()", DateTime.Now);             cao.doSomething();          }          catch (Exception e)          {             Console.WriteLine(" —> EX: Timeout in first call\n{0}",e.Message);          }          Console.WriteLine("{0} CLIENT: Sleeping for 5 seconds", DateTime.Now);          Thread.Sleep(5000);          try          {             Console.WriteLine("{0} CLIENT: Calling doSomething()", DateTime.Now);             cao.doSomething();          }          catch (Exception e)          {             Console.WriteLine(" -> EX: Timeout in second call\n{0}",e.Message );          }          Console.WriteLine("Finished ... press <return> to exit");          Console.ReadLine();       }    } } 
end example

click to expand
Figure 6-4: You've been calling an expired object's method.

There are two ways of correcting this application's issues. First, you can simply increase the object's lifetime on the server as shown in the previous examples. In a lot of scenarios, however, you won't know which TTL will be sufficient. Just imagine an application that acquires a reference to a CAO and will only call another method of this object after waiting for user input. In this case, it might be desirable to add a client-side sponsor to your application and register it with the CAO's lease.

As the first step in enabling your application to work with client side sponsors, you have to include a port="" attribute in the channel section of the configuration file. Without this attribute, the channel will not accept callbacks from the server.

Because you might not know which port will be available at the client, you can supply a value of 0, which allows the .NET Remoting Framework to choose afree port on its own. When the sponsor is created and passed to the server, the channel information that gets passed to the remote process will contain the correct port number.

 <configuration>   <system.runtime.remoting>     <application>       <channels>         <channel ref="http" port="0" />       </channels>       <client url="http://localhost:5555/SomeServer" >               <activated type="Server.SomeCAO, generated_meta" />       </client>     </application>   </system.runtime.remoting> </configuration> 

In the client's code, you then add another class that implements ISponsor. To see the exact behavior of the client-side sponsor, you might also want to add a boolean flag that indicates whether the lease time should be extended or not.

 public class MySponsor: MarshalByRefObject, ISponsor {    public bool doRenewal = true;    public TimeSpan Renewal(System.Runtime.Remoting.Lifetime.ILease lease)    {       Console.WriteLine("{0} SPONSOR: Renewal() called", DateTime.Now);       if (doRenewal)       {          Console.WriteLine("{0} SPONSOR: Will renew (10 secs)", DateTime.Now);          return TimeSpan.FromSeconds(10);       }       else       {          Console.WriteLine("{0} SPONSOR: Won't renew further", DateTime.Now);          return TimeSpan.Zero;       }    } } 

In Listing 6-5 you can see a client application that registers this sponsor with the server object's lease. When the application is ready to allow the server to destroy the instance of the CAO, it will tell the sponsor to stop renewing. Normally you would call Lease.Unregister() instead, but in this case I want to show you that the sponsor won't be contacted further after returning TimeSpan.Zero to the lease manager.

Listing 6-5: Registering the Sponsor to Avoid Premature Termination of the Object

start example
 using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Lifetime; using System.Threading; using Server; // from generated_meta.dll class Client {    static void Main(string[] args)    {       String filename = "client.exe.config";       RemotingConfiguration.Configure(filename);       SomeCAO cao = new SomeCAO();      ILease le = (ILease) cao.GetLifetimeService();      MySponsor sponsor = new MySponsor();      le.Register(sponsor);       try       {          Console.WriteLine("{0} CLIENT: Calling doSomething()", DateTime.Now);          cao.doSomething();       }       catch (Exception e)       {          Console.WriteLine(" -> EX: Timeout in first call\n{0}",e.Message);       }       Console.WriteLine("{0} CLIENT: Sleeping for 5 seconds", DateTime.Now);       Thread.Sleep(5000);       try       {          Console.WriteLine("{0} CLIENT: Calling doSomething()", DateTime.Now);          cao.doSomething();       }       catch (Exception e)       {          Console.WriteLine(" -> EX: Timeout in second call\n{0}",e.Message );       }       Console.WriteLine("{0} CLIENT: Telling sponsor to stop", DateTime.Now);       le.UnRegister(sponsor);       Console.WriteLine("Finished ... press <return> to exit");       Console.ReadLine();    } } 
end example

When you run this application, you will see the output in Figure 6-5 at the client.

click to expand
Figure 6-5: Client-side output when using a sponsor

As you can see in this figure, during the time the main client thread is sleeping for five seconds, the sponsor is contacted by the server. It renews the lease for another ten seconds. Later the sponsor is contacted again but denies the renewal (because the doRenewal field has been set to false). At this time the server-side object will expire.

Instead of telling the sponsor to stop the renewal with a flag, you normally "unregister" it from the lease. You can do this using the following line of code at the point where you're ready to allow the server to destroy the object:

 le.Unregister(sponsor); 
Caution 

When you decide to use client-side sponsors, you have to make sure that the client is reachable by the server.Whenever you are dealing with clients that may be located behind firewalls or proxies, you have to choose another approach!

Using Server-Side Sponsors

Server-side sponsors that are running in the same process as the target CAOs can constitute a solution to the preceding problem, but you have keep in mind several things to make your application run stably.

First, remote sponsors are MarshalByRefObjects themselves. Therefore, they also have an assigned lifetime, and you may want to manage this yourself to provide a consistent behavior. Generally you will want your server-side sponsor to be active as long as the client application is "online." You nevertheless will have to make sure that the resources will be freed as soon as possible after the client application is ended.

One possible approach is to continuously send a command to the sponsors so that they stay alive. This can be accomplished with a simple KeepAlive() method that is called periodically from a background thread of the client application.

Another thing to watch out for is that the sponsor will be called from the .NET Remoting framework's LeaseManager. This call might well increase the time to live of your sponsor, depending on the RenewOnCallTime set in the configuration file. Without taking special care here, you might end up with sponsors that keep running forever when Unregister() has not been called correctly for each and every sponsored object. This could happen, for example, when the client application crashes or experiences a network disconnect.

To remove this potential problem, I suggest you add a DateTime instance variable that holds the time of the last call to the sponsor's KeepAlive() method. When the LeaseManager calls Renew(), the difference between the current time and the last time KeepAlive() has been called will be checked, and the sponsored object's lease will only be renewed when the interval is below a certain limit. As soon as all objects that are monitored by this sponsor are timed out, no further calls will be placed to the sponsor itself, and its own lease will therefore expire as well.

In the following example, I used very short lease times to show you the implications in greater detail. In production applications, you should probably keep this in the range of the default TTL, which is five minutes. The source code in Listing 6-6 shows you the implementation of a sponsor that supports the described functionality.

Listing 6-6: The Server-Side Sponsor That Is Pinged by the Client

start example
 using System; using System.Runtime.Remoting.Lifetime; using System.Runtime.Remoting; using Server; // for ExtendedMBRObject namespace Sponsors {    public class InstanceSponsor: ExtendedMBRObject, ISponsor    {       public DateTime lastKeepAlive;       public InstanceSponsor()       {          Console.WriteLine("{0} SPONSOR: Created ", DateTime.Now);          lastKeepAlive = DateTime.Now;       }       public void KeepAlive()       {          Console.WriteLine("{0} SPONSOR: KeepAlive() called", DateTime.Now);          // tracks the time of the last keepalive call          lastKeepAlive = DateTime.Now;       }       public TimeSpan Renewal(System.Runtime.Remoting.Lifetime.ILease lease)       {          Console.WriteLine("{0} SPONSOR: Renewal() called", DateTime.Now);          // keepalive needs to be called at least every 5 seconds          TimeSpan duration = DateTime.Now.Subtract(lastKeepAlive);          if (duration.TotalSeconds < 5)          {             Console.WriteLine("{0} SPONSOR: Will renew (10 secs) ",                    DateTime.Now);             return TimeSpan.FromSeconds(10);          }          else          {             Console.WriteLine("{0} SPONSOR: Won't renew further", DateTime.Now);             return TimeSpan.Zero;          }       }    } } 
end example

When employing the following configuration file, the sponsor will act like this: a call to KeepAlive() is needed at least every five seconds (determined from the call's time of arrival at the server, so you better call it more often from your client). When this call is received, lastKeepAlive is set to the current time using DateTime.Now and (due to the RenewOnCall time set in the configuration file) its own lease time will be increased to five seconds as well.

Whenever the LeaseManager asks for a renewal, the sponsor will compare the current time to lastKeepAlive, and only when the difference is fewer than five seconds will it extend the sponsored object's lease.

 <configuration>   <system.runtime.remoting>     <application name="SomeServer">       <channels>         <channel ref="http" port="5555" />       </channels>       <lifetime           leaseTime="1S"           renewOnCallTime="1S"           leaseManagerPollTime = "100MS"       />       <service>         <activated type="Server.SomeCAO, Server" />         <activated type="Sponsors.InstanceSponsor, Server" />       </service>     </application>   </system.runtime.remoting>   <appSettings>    <add key="Sponsors.InstanceSponsor_Lifetime" value="5000" />    <add key="Sponsors.InstanceSponsor_RenewOnCallTime" value="5000" />   </appSettings> </configuration> 

Note 

The preceding sample only works with the ExtendedMBRObject shown earlier in this chapter.

As this sponsor's KeepAlive() method needs to be called at regular intervals, you have to add another class to the client application. It will spawn a new thread that periodically calls the sponsor. This class takes an InstanceSponsor object as a constructor parameter and will call the server every three seconds until its StopKeepAlive() method is called.

 class EnsureKeepAlive {    private bool _keepServerAlive;    private InstanceSponsor _sponsor;    public EnsureKeepAlive(InstanceSponsor sponsor)    {       _sponsor = sponsor;       _keepServerAlive = true;       Console.WriteLine("{0} KEEPALIVE: Starting thread()", DateTime.Now);      Thread thrd = new Thread(new ThreadStart(this.KeepAliveThread));      thrd.Start();    }    public void StopKeepAlive()    {       _keepServerAlive= false;    }    public void KeepAliveThread()    {       while (_keepServerAlive)       {          Console.WriteLine("{0} KEEPALIVE: Will KeepAlive()", DateTime.Now);         _sponsor.KeepAlive();         Thread.Sleep(3000);       }    } } 

When implementing this concept in a client application, you again have to run soapsuds -ia:server -nowp -oa:generated_meta.dll and add the newly created CAO to your client-side configuration file:

 <activated type="Sponsors.InstanceSponsor, generated_meta" /> 

In the application itself, you have to add calls to create the server-side sponsor and to start the client-side keepalive thread:

 SomeCAO cao = new SomeCAO(); ILease le = (ILease) cao.GetLifetimeService(); InstanceSponsor sponsor = new InstanceSponsor(); // starting the keepalive thread EnsureKeepAlive keepalive = new EnsureKeepAlive(sponsor); // registering the sponsor le.Register((ISponsor) sponsor); // ... rest of implementation removed 

When you are finished using the CAO, you have to unregister the sponsor using ILease.Unregister() and stop the keepalive thread:

 ((ILease) cao.GetLifetimeService()).Unregister((ISponsor) sponsor); keepalive.StopKeepAlive(); 

Even though in the preceding example I just used a single CAO, you can register this sponsor with multiple leases for different CAOs at the same time. When you run this application, you'll see the output shown in Figures 6-6 and 6-7 on the client and the server, respectively.

click to expand
Figure 6-6: Client-side output when running with server-side sponsors

click to expand
Figure 6-7: Server side output when running with server-side sponsors

In both figures, you can see that KeepAlive() is called several times while the client's main thread is sleeping. The server-side sponsor renews the lease two times before it's finally about to expire.

To see if the application behaves correctly when a client "crashes" while holding instances of the remote object, you can just kill the client after some calls to KeepAlive() and look at the server-side output, which is shown in Figure 6-8.

click to expand
Figure 6-8: Server-side output when the client is stopped during execution

Here you can see that the sponsor processed three calls to KeepAlive() before the client stopped pinging. It received the call to Renewal() more than five seconds later than the last call to KeepAlive(), and therefore refused to further prolong the object's lease time. Hence you can be sure that both objects (the CAO and its sponsor) are timed out and correctly marked for garbage collection.




Advanced  .NET Remoting C# Edition
Advanced .NET Remoting (C# Edition)
ISBN: 1590590252
EAN: 2147483647
Year: 2002
Pages: 91
Authors: Ingo Rammer

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net