Preamble
- Does .NET Remoting implementation in .NET Framework 1.1 have memory leaks?
- Yes.
- Is it possible to avoid them?
- Yes, it’s easy.
- Does it cause any problems to a user who uses Genuine Channels and has read this article?
- No. It’s harmless in this case.
Reproduction
This problem is easily reproducible by all existing channels including Genuine Channels.
The following sample uses the standard TCP channel. You can download it and open the Project.sln file in Visual Studio 2003. Then open Task Manager (CTRL-SHIFT-ESC) and make the “VM Size” column visible. Do not rely on the “Mem Usage” counter because its value is reset every time you minimize and then restore the application window. It is also a good idea to make “Handles” and "Threads” counters visible as well.
The sample is quite simple.
The Known Layer contains only one interface allowing clients to invoke a server object:
|
KNOWN LAYER
public interface IChatRoom { void SendMessage(string message); } |
The server initializes the TCP channel, registers a service provider and waits for client requests:
|
SERVER SIDE
class ChatServer : MarshalByRefObject, IChatRoom { static void Main(string[] args) { // ... RemotingConfiguration.Configure("Server.exe.config"); RemotingServices.Marshal(new ChatServer(), "ChatRoom.rem");
Console.ReadLine(); // ... }
// this method is invoked by client public void SendMessage(string message) { }
// prevents this service from being disconnected from .NET Remoting public override object InitializeLifetimeService() { return null; } } |
The server config file:
|
SERVER SIDE
<configuration> <system.runtime.remoting> <application> <channels> <channel ref="tcp" port="8737"/> </channels> </application> </system.runtime.remoting> </configuration> |
The client implementation is even easier. It configures the .NET Remoting environment and starts sending messages to the server in an infinite loop.
|
CLIENT SIDE
class ChatClient { [STAThread] static void Main(string[] args) { // ... RemotingConfiguration.Configure("Client.exe.config");
// subscribe to the chat event IChatRoom iChatRoom = (IChatRoom) Activator.GetObject (typeof(IChatRoom), ConfigurationSettings.AppSettings["RemoteHostUri"] + "/ChatRoom.rem");
for(;;) iChatRoom.SendMessage("A message.");
// ... } } |
The client config file:
|
CLIENT SIDE
<configuration> <system.runtime.remoting> <application> <channels> <channel ref="tcp" port="0"/> </channels> </application> </system.runtime.remoting> <appSettings> <add key="RemoteHostUri" value="tcp://127.0.0.1:8737" /> </appSettings> </configuration> |
So, as you see, there is nothing serious. Of course, if you start both applications (just press F5 in VS.2003), the server application will start consuming more and more memory. Sometimes it releases a lot of memory, but then quickly regains and exceeds the previous counter value. After a while all applications start working slower and slower and finally you will have to use the RESET or POWER ON/OFF button to bring the server back to life.
Why isn't it easy to locate and identify the problem? Because all objects created in an invocation context are never released.
For example, if you create a simple class:
|
SERVER SIDE
public class aClass {}; |
and then instantiate it in the Server.ChatServer.SendMessage method, instances of this class will never be destroyed:
|
SERVER SIDE
public void SendMessage(string message) { new aClass(); } |
And if you used Allocation Profiler or any of its commercial analogs, you would see that server has as many instances of the aClass class as many times the SendMessage method has been invoked. You can simply add a destructor:
|
SERVER SIDE
public class aClass { ~aClass() { Console.WriteLine(“Called!”); } }; |
You will never see this message even if you call GC.Collect (or GC.Collect(GC.MaxGeneration)) manually.
So, if you try to locate a problem with Allocation Profiler (or with any commercial analog), you just see that all created objects are not released and all of them are not referenced. Garbage Collection just ignores them.
The problem is in .NET Remoting initialization. If an application has this line, it has a memory leak:
| RemotingConfiguration.Configure("any file"); |
The second sample always configures the entire .NET Remoting programmatically. If you comment out the line loading blank configuration file, the server application will not consume memory. If you leave it, the server application has memory leaks:
| RemotingConfiguration.Configure("Server.exe.config"); |
Consequences
The only option you cannot setup programmatically is CustomErrors. The true value of this option enables dispatching caught exceptions back to the caller.
All Genuine Channels always ignore this option and treat it as "off". That is why you will not face with any problems with Genuine Channels. You will have to move the channel and service properties from <configuration> <system.runtime.remoting> <application> to the <configuration> <appSettings> location, parse them manually and get rid of all RemotingConfiguration.Configure calls.
Conclusion
- The problem takes place on the most critical part of solutions - on the server side.
- All existing .NET Remoting channels including Genuine Channels are affected by the problem.
- The problem is caused by the invocation of the RemotingConfiguration.Configure method, no matter what is written in the configuration file.
- You still can use the configuration file without any problems. Just do not call the RemotingConfiguration.Configure method.
- Every time you see that your objects are not processed by Garbage Collection, check your solution on RemotingConfiguration.Configure substring (use the "Find in Files" Visual Studio feature).
- The workaround for this problems will cost you about 4-5 hours of your time, although you may do this more quickly, it depends.
See also:Discuss this article