Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Streams not properly being closed for static files when browser gets cache hit #789

Closed
chiahrens opened this issue Apr 24, 2013 · 14 comments
Assignees
Labels
bug Bug or defect
Milestone

Comments

@chiahrens
Copy link

If the static file is retrieved properly, everything goes through the normal flow where the Stream._transmit is invoked and the stream is closed after the data is piped.

However, if there is a cache hit from the browser on the static file, Hapi executes Generic._transmit, and the readStream opened in response/File is never closed. This eventually results in an error that terminates node:
Error: EMFILE, open '/private/tmp/UIaaS/presentation/serving/lib/client/componentPageMixins.js'

I was able to reproduce this with the following combo:
Node v0.10 with Hapi 0.16
Node v0.10 with Hapi 1.0.0 (cloned from master as of yesterday morning)

@hueniverse
Copy link
Contributor

What is "a cache hit from the browser on the static file"?

@chiahrens
Copy link
Author

The browser requests a static file, but once it gets the timestamp from the metadata, the browser abruptly closes the connection. It looks like the stream is created, but since it's not processed, Node is holding on to the filehandle. I'm looking for a workaround now, but all the node events from 0.8 don't seem to be available or have changed.

@hueniverse
Copy link
Contributor

Looking.

@chiahrens
Copy link
Author

My bad. I didn't realize request.reply() linked to the detailed doc on result. Thanks.

@chiahrens chiahrens reopened this Apr 24, 2013
@chiahrens
Copy link
Author

Sorry. I meant to close the other ticket on docs.

For this, I was able to come up with a quick workaround, but it involved modifying hapi code. If I add the following to response/file.js:

        var stream = Fs.createReadStream(self._filePath, {autoClose: true});
        + request.closeTheseStreams = request.closeTheseStreams || [];
        + request.closeTheseStreams.push(stream);

And then add a tail handler in my code:

    server.on('tail', function (request) {
        if(request.closeTheseStreams && request.closeTheseStreams.length > 0){
            request.closeTheseStreams.forEach(function(s){
                s.close();
            });
        }
    });

It seems to be closing the file handles.

@hueniverse
Copy link
Contributor

{autoClose: true} is already the default. Looking at the rest now.

@hueniverse
Copy link
Contributor

What browser are you using? Trying to reproduce this in a simple app.

@chiahrens
Copy link
Author

Chrome. I have my default ulimit set to like a million. So I open a console, do ulimit -n 25, start node, and then hit a static file in Chrome and keep doing refresh until it starts erring - usually like 15 refreshes.

hueniverse pushed a commit that referenced this issue Apr 25, 2013
@hueniverse
Copy link
Contributor

Try this and let me know if it works.

@ghost ghost assigned hueniverse Apr 25, 2013
@chiahrens
Copy link
Author

That doesn't work because at some point, when the browser abruptly disconnects, hapi decides to use the generic response after the file stream is created instead of the stream response, so none of that code is executed.

Here is a simple test that I used:

  1. mkdir -p test/htdocs && echo "{}" >> test/htdocs/test.css && cd test
  2. Create a file app.js with the following:
var Hapi = require('hapi');
var server = new Hapi.Server('0.0.0.0', 8080);
server.route({
    method: 'GET',
    path: '/{path*}',
    handler: {
        directory: { path: "htdocs"}
    }
});
server.start();
  1. npm install hapi (from master)
  2. node app.js
  3. In another console, run "ps -ef|grep node" to get the pid of the node process. Use that pid and run 'lsof -p |grep "test.css"' to get the test.css file descriptors being held open by that process.
  4. In the console, do "curl http://localhost:8080/test.css" and when you check the number of open files with lsof, you will see that they're being closed properly.
  5. Now go to chrome, and browse to curl http://localhost:8080/test.css. Hit refresh a few times and run the lsof command again, and you will see that the open files just being held by the process.

@chiahrens
Copy link
Author

So it's this code in index.js that causes it to bypass the stream._transmit, and instead calls generic._transmit if file has not been modified:

        if (ifModifiedSinceHeader &&
            lastModifiedHeader) {

            var ifModifiedSince = Date.parse(ifModifiedSinceHeader);
            var lastModified = Date.parse(lastModifiedHeader);

            if (ifModifiedSince &&
                lastModified &&
                ifModifiedSince >= lastModified) {

                var unchanged = new internals.Empty();
                unchanged._code = 304;
                return prepare(unchanged);
            }
        }

@chiahrens
Copy link
Author

Oh yeah, so I was completely wrong about the abruptly disconnecting part. It's actually a graceful disconnect after hapi checks the timestamp. I was assuming that this was Node's behavior based on my past experience with other similar app servers that did abruptly abort connections.

@chiahrens
Copy link
Author

The following change in response/index.js is a working hack:

                unchanged._code = 304;
                + response._stream && response._stream.close && response._stream.close();
                return prepare(unchanged);

Strangely, stream.destroy() doesn't seem to close the filestream.

@hueniverse
Copy link
Contributor

@chiahrens Thanks for your help with this. I've verified the fix with your scenario. The file stream is no longer opened unless it is being used.

@joliss joliss mentioned this issue Feb 6, 2014
jmonster pushed a commit to jmonster/hapi that referenced this issue Feb 10, 2014
jmonster pushed a commit to jmonster/hapi that referenced this issue Feb 10, 2014
@lock lock bot locked as resolved and limited conversation to collaborators Jan 13, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Bug or defect
Projects
None yet
Development

No branches or pull requests

2 participants