-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
http: add writeFile to OutgoingMessage #50576
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
Conversation
Review requested:
|
389eda2
to
277b2b8
Compare
@nodejs/performance Could I maybe get some help with creating a relevant benchmark? |
One of the most common usages of http servers are to response with files. This provides a faster alternative to: stream.pipeline(fs.createReadStream(path, { start, end }), res, callback) PR-URL: nodejs#50576
One of the most common usages of http servers are to response with files. This provides a faster alternative to: stream.pipeline(fs.createReadStream(path, { start, end }), res, callback) PR-URL: nodejs#50576
One of the most common usages of http servers are to response with files. This provides a faster alternative to: stream.pipeline(fs.createReadStream(path, { start, end }), res, callback) PR-URL: nodejs#50576
One of the most common usages of http servers are to response with files. This provides a faster alternative to: stream.pipeline(fs.createReadStream(path, { start, end }), res, callback) PR-URL: nodejs#50576
One of the most common usages of http servers are to response with files. This provides a faster alternative to: stream.pipeline(fs.createReadStream(path, { start, end }), res, callback) PR-URL: nodejs#50576
One of the most common usages of http servers are to response with files. This provides a faster alternative to: stream.pipeline(fs.createReadStream(path, { start, end }), res, callback) PR-URL: nodejs#50576
One of the most common usages of http servers are to response with files. This provides a faster alternative to: stream.pipeline(fs.createReadStream(path, { start, end }), res, callback) PR-URL: nodejs#50576
One of the most common usages of http servers are to response with files. This provides a faster alternative to: stream.pipeline(fs.createReadStream(path, { start, end }), res, callback) PR-URL: nodejs#50576
One of the most common usages of http servers are to response with files. This provides a faster alternative to: stream.pipeline(fs.createReadStream(path, { start, end }), res, callback) PR-URL: nodejs#50576
One of the most common usages of http servers are to response with files. This provides a faster alternative to: stream.pipeline(fs.createReadStream(path, { start, end }), res, callback) PR-URL: nodejs#50576
One of the most common usages of http servers are to response with files. This provides a faster alternative to: stream.pipeline(fs.createReadStream(path, { start, end }), res, callback) PR-URL: nodejs#50576
One of the most common usages of http servers are to response with files. This provides a faster alternative to: stream.pipeline(fs.createReadStream(path, { start, end }), res, callback) PR-URL: nodejs#50576
One of the most common usages of http servers are to response with files. This provides a faster alternative to: stream.pipeline( fs.createReadStream(path, { start, end }), res, callback ) PR-URL: nodejs#50576
One of the most common usages of http servers are to response with files. This provides a faster alternative to: stream.pipeline( fs.createReadStream(path, { start, end }), res, callback ) PR-URL: nodejs#50576
You can use docker and I'm pretty sure you can can also find the fd in Mac Also, I'm pretty sure that we save file descriptors references in the fs file |
Sorry, that kind of test is beyond me. If we have some existing test I can use as a reference I'm happy to add it. Otherwise, a follow-up PR is welcome. There are many other places that are similar that could also use such a test, e.g. file streams. |
One of the most common usages of http servers are to response with files. This provides a faster alternative to: stream.pipeline( fs.createReadStream(path, { start, end }), res, callback ) PR-URL: nodejs#50576
One of the most common usages of http servers are to response with files. This provides a faster alternative to: stream.pipeline( fs.createReadStream(path, { start, end }), res, callback ) PR-URL: nodejs#50576
One of the most common usages of http servers are to response with files. This provides a faster alternative to: stream.pipeline( fs.createReadStream(path, { start, end }), res, callback ) PR-URL: nodejs#50576
callback(err, bytesWritten); | ||
}); | ||
} else if (typeof file === 'string' || isURL(file)) { | ||
fs.open(getValidatedPath(file), (err, fd) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a test to the permission model? Just in case someone changes this implementation for a function that doesn't handle permissions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have any good tests I can look at for reference?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this implementation should be done in C++. Performance wise this is not looking good.
doc/api/http.md
Outdated
@@ -3187,6 +3187,55 @@ Removes a header that is queued for implicit sending. | |||
outgoingMessage.removeHeader('Content-Encoding'); | |||
``` | |||
|
|||
### `outgoingMessage.sendFile(pahtOrFd, optsOrCallback, callbackOrNil)` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
### `outgoingMessage.sendFile(pahtOrFd, optsOrCallback, callbackOrNil)` | |
### `outgoingMessage.sendFile(pathOrFd, optsOrCallback, callbackOrNil)` |
callback(err, bytesWritten); | ||
}); | ||
} else if (typeof file === 'string' || isURL(file)) { | ||
fs.open(getValidatedPath(file), (err, fd) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line has missing toNamespacedPath
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's that? Also fs streams don't have any reference to toNamespacedPath? Is it missing there as well?
} | ||
}; | ||
|
||
function _writeFile(fd, res, start, end, callback) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this function should be implemented in C++. There are too many JS -> C++ boundary calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Follow up PR welcome 🙏
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There may be a challenge implementing this in c++ given how much of the implementation of OutgoingMessage
is in JavaScript.
doc/api/http.md
Outdated
added: REPLACEME | ||
--> | ||
|
||
* `file` {string|number|URL} file path, URL or descriptor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The order of types and the descriptions are not same.
20f3904
to
5f91a90
Compare
|
||
> Stability: 1.1 - Active development. | ||
|
||
* `file` {string|URL|number} file path, URL or descriptor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably worthwhile for this to support FileHandle
also.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some issues with filehandle I'd like to sort out before that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would recommend against using that api in general due to issue with how the reference counting behaves.
req.resume(); | ||
fs.open(fname, (err, fd) => { | ||
assert(!err); | ||
res.writeFile(fd, (err) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also include tests verifying the function of start
and end
as well as input argument validation
I might have been a bit naive in how much effort this would take. Tests is half the job. At this stage with no native implementation this is possible to just add in user space. Which is good enough for our use case. |
Here is the user space implementation for those that want it: function writeFile(fd, res, start, end, callback) {
const hwm = res.writableHighWaterMark;
let reading = false;
let pos = start;
let resume = null;
let bytesWritten = 0;
const finish = (err) => {
if (resume != null) {
res
.off('drain', resume)
.off('close', resume);
}
callback(err, bytesWritten);
}
const next = (err, bytesRead, buf) => {
reading = false;
if (pos !== undefined) {
pos += bytesRead;
}
bytesWritten += bytesRead;
buf = bytesRead === 0 ? null : bytesRead < buf.byteLength ? buf.subarray(0, bytesRead) : buf;
const wState = res.socket && res.socket._writableState;
if (res.destroyed) {
finish(null);
} else if (err && err.code === 'EAGAIN') {
setImmediate(read);
} else if (err || buf === null) {
finish(err);
} else if (res.write(buf)) {
read();
} else if (wState && wState.length - wState.writelen < hwm) {
// In order to ensure we don't stall we need to continue
// reading the file while the socket is writing.
read();
} else if (resume == null) {
resume = read;
res
.on('drain', resume)
.on('close', resume);
}
};
const read = () => {
if (reading) {
return;
}
reading = true;
const n = pos !== undefined ?
Math.min(end - pos + 1, hwm) :
Math.min(end - bytesWritten + 1, hwm);
assert(n >= 0);
if (res.destroyed) {
finish(null);
} else if (n === 0) {
next(null, 0, null);
} else {
fs.read(fd, Buffer.allocUnsafe(n), 0, n, pos, next);
}
};
read();
} |
Provide an optimized path for sending files over HTTP. Removes the entire memory and processing overhead of creating and operating an intermediate Readable.