Dart HttpServer Exhausts Heap Space
Asked Answered
V

1

6

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.

Viceroy answered 2/11, 2012 at 1:43 Comment(1)
"This makes me weary of using Dart for a full scale project.": Every project has bugs (unless your last name happens to be Knuth). We've been focusing a lot more on client-side code than server-side code so far in Dart. The server-side will start to improve more as the client-side starts to simmer down. Thanks again for your bug report!Incautious
I
4

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.

Incautious answered 2/11, 2012 at 17:44 Comment(5)
Why wouldn't it close the file to do that? I've checked and it seems file.close() is not a method that exists, so it should be automatically handled? And I commented it out for reasons that I don't remember. I'm testing with your solution now.Viceroy
Tested it, and it doesn't fix the issue. Dart was using 1.5GB of RAM after having only transferred 25MB total to 106,000 requests generated over 104 seconds. This is serving up a simple html file that isn't very large.Viceroy
I'm sure it's easy to do a DoS against the server if you just open up a bunch of requests and never read anything back, or if you prioritize opening new requests over reading stuff back. I don't think the server has been hardened against that sort of abuse yet. Can you set a limit of 500 concurrent transactions? Have you tried ab (Apache bench; it comes with Apache) as well? It may be time to file a bug on dartbug.com.Incautious
coder543, can you make sure to "green check" this answer? Thanks!Incautious
I wasn't doing a DoS attack by any means, I was doing around 100 concurrent users and each request was fully completed. There was no attack, it was just a benchmark. However, because you updated to link to a bug report, I'll mark your answer as correct, as even though it is not the solution, it verifies the problem.Viceroy

© 2022 - 2024 — McMap. All rights reserved.