This project has moved. For the latest updates, please go here.

Memory Leak

Sep 7, 2010 at 8:08 AM

hi,shammah.
I have writed a application (client/server) using Asynchronous Sockets.when client send a file(200M+) to server,
the client will use 300M+ RAM,but server look fine.

mock code as below:

bool eof;
byte[] fileBuffer;

while(!eof)
{
 fileBuffer = GetNextChunk(out eof);
 SocketPacketProtocol.WritePacketAsync(clientSocket, fileBuffer);
}

//Get file buffer from a FileStream
private byte[] GetNextChunk(out bool eof)
{
 ...
}

------------------------------------------------------------------------------------------------------
i debug and profile the client app,found problem is caused by class WriteAsync method of TcpSocketImpl class.

public void WriteAsync(byte[] buffer, int offset, int size, object state)
{
 Socket.BeginSend(buffer, offset, size, SocketFlags.None, Sync.SynchronizeAsyncCallback((asyncResult) =>
  {
   Sync.InvokeAndCallback(() => Socket.EndSend(asyncResult),
    OnWriteCompleted, asyncResult.AsyncState);
  }), state);
}

when i change the code as :

public void WriteAsync(byte[] buffer, int offset, int size, object state)
{          
 Socket.Send(buffer, offset, size, SocketFlags.None);
}

the client will wrok fine.(used RAM 20M-30M).

via google,i found the KB:http://support.microsoft.com/kb/947862,but i don't known how to do
"The network application should have an upper bound on the number of *outstanding* asynchronous IO that it posts."

How to sovle the problem via Asynchronous Sockets library?

Thanks in advance.

Coordinator
Sep 7, 2010 at 5:09 PM

You're seeing high memory usage because your program is loading the entire file into memory as it's being sent over the network. Note that this is not a "memory leak;" it is expected behavior.

Disk operations are quite fast when compared to network operations. So the "while (!eof)" loop is actually running very fast, queueing up all the chunks of the file to the socket. Each queued chunk is still taking up memory while it's waiting for all the chunks before it to get sent out over the network.

This doesn't happen when changing to blocking operations because they block on the network, so they don't read every chunk immediately.

In order to solve this asynchronously, you'll have to re-write the "while (!eof)" loop to be asynchronous. Some asynchronous "state" will be required, at least to track how much of the file has been read. Then call one of the WriteAsync/WritePacketAsync overloads that takes an "Object state" parameter. Handle the "WriteCompleted" event and use the "e.UserState" argument to continue reading the file.

Something like this:

public class MyClass
{
  private IAsyncTcpConnection clientSocket;

  public MyClass()
  {
    this.clientSocket = ..;
    this.clientSocket.WriteCompleted += args =>
    {
      // TODO: add in normal error handling code here
      if (e.UserState is MyClass)
      {
        this.ContinueWritingFile();
      }
   };
  }

  // Start the asynchronous load-and-send-file operation.
  public void StartWritingFile()
  {
    // TODO: need some sort of protection against starting multiple file writes.
    this.ContinueWritingFile();
  }

  // Continue the asynchronous load-and-send-file operation.
  private void ContinueWritingFile()
  {
    bool eof;
    byte[] buffer = this.GetNextChunk(out eof);
    if (!eof)
    {
      SocketPacketProtocol.WritePacketAsync(this.clientSocket, buffer, this);
    }
  }

  //Get file buffer from a FileStream
  private byte[] GetNextChunk(out bool eof)
  {
    ..
  }
}

This way, the loading from the file only happens as the network buffers get empty again. So we're using the slow operation (network) to drive the fast operation (disk) in an asynchronous manner.

Note that you will have to think about your API regarding how you want to guard against multiple "load and send file" operations going at once. A simple boolean flag with an InvalidOperationException is an easy solution; if you need more complexity, a queue of work may be necessary.

       -Steve

Sep 8, 2010 at 9:00 AM

thanks,Steve.

It's work fine now.by i still confused:before i change code as you said,when file send completed,why the GC don't collect the Large Ram of socket buffer?

I just found a nice article "C# SocketAsyncEventArgs High Performance Socket Code" on codeproject:http://www.codeproject.com/KB/cs/socketasynceventargs.aspx.

and how do you think about this article?

thandks

-Ken

 

Coordinator
Sep 8, 2010 at 1:38 PM

Ken,

The original code was still referencing the file chunks in memory. They were waiting to be queued to the socket, so they were not eligible for GC.

I haven't read the article you referenced, but there are two reasons why SocketAsyncEventArgs aren't currently used in Nito.Async.Sockets:

  1. There have been (unconfirmed) rumors of bugs under heavy load. Not enough to say for sure that it's "buggy," but enough to make me want to go over all the code searching for multithreading bugs with a fine-toothed comb before I'd use it.
  2. In order to use SocketAsyncEventArgs in a way that is more efficient than Begin/End, the program must know a good deal about how it normally uses sockets and manage its SocketAsyncEventArgs pool accordingly. This is difficult for a generic socket library; I'd have to either define a whole "pool interface" that clients are required to implement, or implement a default pool (but have tons of configurable knobs).

Still, it is something I'm considering for the future, mainly for Silverlight compatibility (Silverlight does not support the Begin/End methods for sockets).

       -Steve

Sep 9, 2010 at 7:48 AM

Steve,

Have you considering for Compact Framework compatibility?Many device application(windows mobile,windows phone...) need  a nice socket library.

-Ken

 

Coordinator
Sep 9, 2010 at 5:01 PM

Yes, but it probably won't happen. At my former job, I had to work on a few CF projects. One of the problems with CF is that it does not support SynchronizationContext, which is kind of the central theme of Nito.Async. So, it would take quite a bit of work to either port Nito.Async.Sockets to CF Windows Forms, ignoring the rest of Nito.Async (a solution I do not find appealing); or to partially implement SynchronizationContext for CF (which may interfere with other libraries or users doing the same thing).

Unfortunately, I believe that CF is on the way "out." Microsoft appears to be totally focused on Windows Phone 7 and its Silverlight support. This does leave non-phone mobile devices a bit out in the cold, and time alone will tell what happens to them.

So I have considered CF support, but am not planning on it at this time. Silverlight support is much more likely.

       -Steve