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

Avoid race condition by using fs.open and fs.fstat, also allow file descriptor to be passed #125

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

jcready
Copy link
Contributor

@jcready jcready commented Dec 17, 2016

Adds two new options:

  1. fd - If specified, send will ignore the path argument and will use the specified file descriptor. This means that no 'open' event will be emitted.
  2. autoClose - If false, then the file descriptor won't be closed, even if there's an error. It is your responsibility to close it and make sure there's no file descriptor leak. If set to true (default behavior), on error or end the file descriptor will be closed automatically.

It also uses fs.open, fs.fstat and passes the file descriptor to fs.createReadStream to ensure there is no race condition where the the file located at the specified path is unlinked, renamed, or replaced in between the current calls to fs.stat and fs.createReadStream which would result in an incorrect response.

I've also renamed onStatError to onFileSystemError to more accurately reflect what types of errors it is responsible for handling.

Resolves #122
Resolves #123

@jcready jcready changed the title Allow user to specify a file descriptor and avoid race condition by using fs.open and fs.fstat Avoid race condition by using fs.open and fs.fstat, also allow file descriptor to be passed Dec 17, 2016
@dougwilson dougwilson self-assigned this Dec 18, 2016
@dougwilson dougwilson self-requested a review December 18, 2016 07:02
@@ -15,7 +15,6 @@
var createError = require('http-errors')
var debug = require('debug')('send')
var deprecate = require('depd')('send')
var destroy = require('destroy')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usage of the destroy module is to work-around a fd leak in Node.js 0.10 and lower. I don't see the work-around reproduced in here from the removal, so does this mean that this PR would require Node.js 0.12+ ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or I guess it's no longer relevant since I think the changes here are to manually manage the fd instead of letting Node.js's read stream do so?

Copy link
Contributor Author

@jcready jcready Jan 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dougwilson that is correct. I've even added afterEach assertions to make sure that all open file descriptors have been closed after every test has run. Although, to be honest, supporting node < 0.10 does make working with streams much more tricky. Node >= 0.10 has the Stream2 implementation and supports the autoClose parameter when using fs.ReadStream. To support the autoClose option I'm exposing in send() I have to overwrite the fs.ReadStream instance's destroy() method to ensure that it won't close the file descriptor.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, makes sense. And to be clear, the fd leak is in Node.js 0.10 and lower, which includes 0.10, not sure if there was some confusion around that, so just checking :)

All modules in our organization will be adopting 0.10 as the minimum version as they get their major versions bumped currently. Some may get an even higher minimum version if warranted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dougwilson yeah the fd leak shouldn't be a problem since we're now opening and closing the fd ourselves instead of relying on fs.createReadStream() to do it for us. The "destroy" module was essentially just adding an .on('open', close) handler to the stream.

Regarding the min Node version being supported, when do you see that happening? Even bumping the min version to 0.10 would be handy since it will respect the autoClose option by default.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha. Regarding min Node.js, the current plan is to hold it until the next major version bump of this module (required by semver, though this is 0.x so technically not, I try to use that as the 1.x bump). That timeline is still TBD, but likely will correspond to around the same time Express 5.0 is slated to be released. Since Express is run by volunteer, there is no date until everything is done, at which point a date is added. I foresee at least a few more months.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dougwilson so after you mentioned the fd leak I took a hard look at all the logic and it turned out you were right to bring it up. There were two situations where the fd would be leaked: 1) the res stream ends before it is passed to sendStream.pipe() and 2) the res ends after being passed to sendStream.pipe() but before sendStream.send() is called. I have accounted for these two situations in my latest commit and have added a few tests for them.

of returning an error code of `EBADF` it returns
an error code of `OK` which makes zero sense
PartStream.prototype.destroy = PartStream.prototype.close = function closePartStream () {
this.readable = !(this.destroyed = this.closed = !(this.fd = null))
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class can be eliminated if support for node < 0.10 is dropped. Please do let me know if that change is going to be happening soon because it will have a much higher impact on the multipart/byteranges support I'm working on.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @jcready sorry I didn't reply right away, was on a trip and then forgot :) I just replied to your previous question on Node.js support and the TL;DR version is at least a few more months of 0.8.

1. The `res` ends before it is passed to `send().pipe()`
2. The `res` ends after pipe but before the `open` event
# Conflicts:
#	index.js
#	test/send.js
# Conflicts:
#	README.md
#	package.json
#	test/send.js
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow sending file descriptor Open file with fs.open and use fs.fstat and createWriteStream(null, { fd })
2 participants