The technical design of most relative complex .NET applications requiring remoting instructs using of remoting calls at a number of different places. The goal of this article is not to describe remoting technology in details. It is more focused at the practical design and implementation mistakes during development process concerning garbage collection and performance.
From developers point of view, generally remoting can be divided in two categories: Client calls and Server object hosting.
Server Object Hosting
The servers in .NET applications can host remoting objects as "singleton" and as "single call". For more information about this please see MSDN documentation.
Let's assume we have a server application hosting some remoting object called ObjectFactory. The ObjectFactory type exports a number of public methods doing something.
But, one of methods should create instance of some specified object and return it to the caller. Please note, that this scenario is very common:
The client invokes some remoting method, which returns an object!
In this example I provided a method, which dynamically creates the specified object contained in some assembly and returns it to the caller.
This method is defined as follow:
object
Create (Type someType)
One of possible implementations of this method is show in the next example.
Example 1:
This example shows the implementation of some ObjectFactory.Create() method creating dynamically the instance of specified object. As you see, the current version supports the creation of instance of MyObject type only.
Additionally, it is assumed that MyObject type is implemented in the assembly named MyAssembly.
Please ensure that in this case the named assembly must be reachable by the server application. It has to be stored either in the servers application directory or registered in the Global Assembly Cache.
public
object Create(Type interfaceType)
{
if (interfaceType == typeof(MyObject))
{
Assembly assembly = Assembly.Load("MyAssembly");
object obj = assembly.CreateInstance("MyNamespace.MyObject");
return (MyObject)obj;
}
else
return null;
}
Note that the code above shows only one possible scenario haw to create the instance of an object!
It is important to know that the object retrieved by "remoting" Create() method must be serializable as shown.
namespace
MyNamespace
{
public class MyObject : MarshalByRefObject
{...}
}
Depending on application requirements the live time of the object can often be set as shown:
namespace
MyNamespace
{
public class MyObject : MarshalByRefObject
{
public override Object InitializeLifetimeService()
{
// This lease never expires.
return null;
// Use following code to define the exact lease time.
ILease lease = (ILease)base.InitializeLifetimeService();
if (lease.CurrentState == LeaseState.Initial)
{
lease.InitialLeaseTime = TimeSpan.FromSeconds(5);
lease.SponsorshipTimeout = TimeSpan.FromSeconds(0);
lease.RenewOnCallTime = TimeSpan.FromSeconds(5);
}
return lease;
}
}
}
When the Create() method is invoked multiple times (see bellow) and the lease time is set to null (never expires or infinite) then the server's working space increases, because the garbage collector at the server side does not collect dynamically created instances!
Following client-code illustrate successive invocation of the Create() mehod from the client side and forces the server to create leaks if the lease time of MyObject is null (never expires or infinite).
for
(int n=0;n<100000;n++)
{
MyObject myObj=(MyObject)objFactory.Create(typeof(MyObject));
}
Changing of lease time of the object can be very dangerous. Most developers expect the Garbage Collector takes a care about collecting of objects. Note that by hosting of remoting instances the Garbage Collector does not know whether the object is still used at the client side.
This is where the Lese Manager plays important role.
If the "Lease Time" is set to some value (e.g. 2 seconds), then the framework collects unreferenced objects as expected!
This is a simple solution, but in most scenarios is very difficult to deal with object whose instances live for a short time.
Some developers can say now "no problem, let's set lese time to 10 minutes"!
This could help to deal with remoting objects much easier, but what if the application must create thousands of such objects per second?!
In this case the working space of server application would increase so much, that whole application would probably crashes after some time.
Note, that releasing of instances of remote objects might be a confusing feature of .NET which can be explained as follow.
Let's slightly change the Create() method of the ObjectFactory as shown in the next example:
Example 2:
In this example is assumed, that the lease time of MyObject is set to infinite!
Public object Create(Type interfaceType)
{
if (interfaceType == typeof(MyObject))
{
Assembly assembly = Assembly.Load("MyAssembly");
Object obj = assembly.CreateInstance("MyNamespace.MyObject");
return null;
}
else
return null;
}
The difference here is that the Create() method this time does not retrieve the created object at all. In this case the server's working space does not increase, because the garbage collector at the server side (not "Lease Manager") takes a care about unreferenced instances!
Once the server retrieves some object using remoting, the garbage collector does not know whether the object is still referenced (needed) at the client side.
In this case the server application domain's lease manager is the object that determines when the remote object is marked for garbage collection.
Conclusion
When some remoting object derived from MarshalByRefObject (invocation by reference) is transferred over network (serialized, see Example 1) then the garbage collector does not take a care about object references to collect them as expected.
Instead, the framework's Lease Manager takes a responsibility about object's lifetime.
This is so, because the framework does not check regularly the connection with the client and cannot know anything about "using of remote instance". The idea behind this concept is to reduce network traffic.
Otherwise, if one object is of TransparentProxy type then do not expect that garbage collector simply free this object if it has been transferred to the client!
Solution 1:
To avoid possible "leak" problems and to increase the performance the CDS implementation should simply provide singleton instances of all objects with infinite lease time instantiated by the ObjectFactory.Create() method.
Please note that in this case the singleton implementation has to be thread safe!
Solution 2:
If you cannot avoid using of infinite lease time and cannot implement a MyObject as singleton then provide a method or any other functionality to release created instances.
Solution 3:
Additionally, the lease time of all objects created by ObjectFactory could be set to some arbitrary value. In this case you would not get any synchronization problem, but the performance of the system would be decreased.
Client Calls
Let's assume your application provides a mechanism of routing of messages from host to host using .NET remoting.
The simplest way to send a message to the .NET remoting server application looks as shown:
- Create the proxy of the ObjectFactory
- Create the instance of the remoting object by calling ObjectFactory.Create() method
- Call remote method to send the message
This can be illustrated in following example:
Example 1:
public
static void f()
{
for(int n=0;n<100000;n++)
{
Line 1: ErsObjectFactory objFactory = new ObjectFactory();
Line 2: MyObject myObject = (MyObject)objFactory.Create(typeof IADTEventProcessing));
Line 3: myObject.SomeMethod(...);
}
}
Assume now that the server connection is broken when the program counter is finished with the Line 1. If we try to execute the next line (Line 2) then an exception will be thrown.
But, if the server is started again (when the program counter is at the Line 2) and then we try to execute the Line 2, the connection with the server will be established without any problem.
This means, that the objFactory object of TransparentProxy type is an off-line object having all necessary information about the remoting object, which does not have to be created each time the message has to be sent.
One client application needs to create this object once only! Do not try to restore the proxy object if the server has crashed.
This is a redundant and time very expensive operation.
By obtaining of the TransparentProxy object, the .NET framework calls underlined method RealProxy.PrivateInvoke, which is also called each time when some remoting method is invoked. The difference is that by obtaining of the TransparentProxy object no any data is sent over network.
But, in both cases PrivateInvoke method is very time consuming and should not be called if not necessary!
Note, that proxy object after its creation is thread safe!
Example 2:
Following example shows how to create the optimized remote call successively.
public
static void f1()
{
ObjectFactory objFactory = new ObjectFactory();
for(int n=0;n<100000;n++)
{
MyObject myObj = (MyObject)myObject.Create(typeof(MyObject));
myObject.SomeMethod(...);
}
}