Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dart HttpServer Exhausts Heap Space

I've been modifying some sample code to use for a web server, this way I can have dart running on both the server and the client. However, I decided I wanted to check out the performance of the web server, and I'm mostly impressed, except for the crashing. I'm using the "siege" package on Ubuntu to generate lots of traffic to the website using a handful of URLs.

I've seen it delivering a little north of 1000 transactions per second, which is very acceptable for me, but after two to three minutes run time it either crashes or hangs (if I increase the new_gen_heap_size).

#import('dart:io');

class FCServer {

String basePath;

void send404(HttpResponse response)
{
    response.statusCode = HttpStatus.NOT_FOUND;
    response.outputStream.close();
}

void handleRequest(HttpRequest request, HttpResponse response)
{
  final String path = request.path == '/' ? '/index.html' : request.path;
  final File file = new File('${basePath}${path}');
  file.exists().then((bool found) {
      if (found)
      {
          file.fullPath().then((String fullPath) {
              if (!fullPath.startsWith(basePath))
              {
                  send404(response);
              }
              else
              {
                  //print("delivering $fullPath");
                  response.headers.add("Cache-Control", "max-age=3600");
                  //file.openInputStream().pipe(response.outputStream);
                  var file = new File("$fullPath");
                  //response.headers.set(HttpHeaders.CONTENT_TYPE, "$contentType; charset=UTF-8");
                  response.outputStream.write(file.readAsBytesSync());
                  response.outputStream.close();
              }
         });
        }
        else
        {
            send404(response);
        }
    }); 
}

void startServer(String basePath)
{
    this.basePath = basePath;
    var server = new HttpServer();
    print("Starting server with basePath: $basePath");
    server.listen('192.168.0.14', 8080);
    server.listen('127.0.0.1', 8080);
    server.defaultRequestHandler = handleRequest;
}
}

FCServer webServe;
main()
{
    // Compute base path for the request based on the location of the
    // script and then start the server.
    webServe = new FCServer();
    File script = new File(new Options().script);
    script.directory().then((Directory d) {
        webServe.startServer(d.path);
    });
}

The error that Dart prints before exiting is as follows:

Exhausted heap space, trying to allocate 128096 bytes.
Exhausted heap space, trying to allocate 112 bytes.
Exception 'Instance of 'OutOfMemoryException'' thrown:
Exiting the process

Does this mean that there is a memory leak somewhere? Or can someone explain what is going on?

EDIT: when the new_gen_heap_size is increased to 1024, it does hang after awhile and a single thread sits at 100% whether requests are incoming or not. At this point the RAM is up to 1.5GB after a run through with the aforementioned heap size, so I assume the garbage collector has kicked the bucket, so to speak.

EDIT 2: I've modified the code so that on initialization it creates a List consisting of 4 bytes, then every time a request is made it simply writes that List to the response and closes the response. The memory usage still grows rapidly, indicating a problem deeper within Dart. This makes me weary of using Dart for a full scale project.

like image 898
coder543 Avatar asked Oct 07 '22 03:10

coder543


1 Answers

Why did you comment out "file.openInputStream().pipe(response.outputStream);" and replace it with "response.outputStream.write(file.readAsBytesSync());"? There are three problems with that:

  • It doesn't close the file when it's done reading it.
  • It isn't asynchronous.
  • It reads all of the bytes at the same time, rather than streaming them to the client.

If you happen to be serving a really big file, and you have a very large number of concurrent requests, you'll have a lot of copies of that file in memory.

Does using "file.openInputStream().pipe(response.outputStream);" make the problem go away?

I feel confident that "response.outputStream.write(file.readAsBytesSync());" is the problem. Also, remember that .write doesn't write the bytes immediately. It buffers them up to be written. Hence, you have a copy of the entire file buffered up to be written.

Updated: Mads Ager verified that it was a memory leak, and he's already submitted a fix for it. Here's the bug he filed.

like image 172
Shannon -jj Behrens Avatar answered Oct 10 '22 04:10

Shannon -jj Behrens