-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
vscode: fix binary is not functional on windows #3092
Conversation
Seems reasonable! |
Switched to |
Wait, rechecked on windows, and this damn pipeline api doesn't actually work ... |
God, I love Node's API. Will revert to the first impl, since it does work... |
363d80b
to
78ee964
Compare
Now we are ready to merge |
Just trying to understand: what happens if there is a write error? Is there the possibility that the promise will stay in the pending status forever? |
This would be unfortunate, though yes, I thought about it and handling double reject would bring some amount of "who-knows-what-it-is-doing" code. For this reason I tried to switch to stream.pipeline() API, but it doesn't resolve when the write-stream is closed either. I'll try to come up with better error handling here, but in a separate PR, let's leave this one as one particular fix an nothing more |
bors r+ |
Build succeeded
|
@marcogroppo , I've just checked ECMAScript specs. And found that we do can call |
.on("error", reject) | ||
.pipe(fs.createWriteStream(destFilePath)) | ||
.pipe(fs.createWriteStream(destFilePath).on("close", resolve)) |
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.
It's recommended to use stream.pipeline
in all cases over stream.pipe
. Formerly this functionality was only available in the pump
and pumpify
packages, but was added two years ago to fix the problems around pipe
(in particular: error handling, and correctly detecting close events -- as witnessed here). Also when used with util.promisify
it allows simplifying Promise construction for the pipeline:
const pipeline = util.promisify(stream.pipeline);
let bs = response.body;
.on("data", (chunk: Buffer) => {
readBytes += chunk.length;
onProgress(readBytes, totalBytes);
});
await pipeline(bs, fs.createWriteStream(destFilePath);
nb: I used to be on the node streams wg up until a few years ago 😅
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.
@yoshuawuyts , thank you for the suggestion, I did look into stream.pipeline()
, but then reverted back, since it doesn't resolve the promise when the write-stream is closed. You can see my comments about trying to incorporate it in this PR, but still it didn't fix the error, but my initial approach with .on("close")
did, anyway... Maybe this is a bug in NodeJS?
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.
Though, no, I cannot reproduce it again with stream.pipeline()
... I need to recheck it on my home laptop where I did saw it was not working. Or maybe I just was overwhelmed by the bug and somehow was running the wrong code? I am not sure... Thank you for the pointer anyway, I'll double-recheck that stream.pipeline()
does resolve the promise when the write-stream is closed today!
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.
Though, no, here is a gist of the reproduction.
When I run it, I get RESOLVED
printed before CLOSED
, though there is no EBUSY
error on my current PC...
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.
Okay, so this turned out to be a fun one.
In the example you're listening for the "close"
event, which you would rightfully assume is called "at the end of the pipeline". However that's not what it does: it's emitted when the underlying file descriptor is closed. Which because Node.js doesn't have destructors it's emitted when the program is closed: which means it's last.
However if you listen for the "finish"
event instead, it will trigger when you expect it to: namely when the writing to the writer has finished. The reason this doesn't also close the file descriptor is because it's possible to reuse the same write stream in multiple pipelines and the descriptor should be kept open.
If you want to close the file descriptor after the pipeline has finished, call .destroy()
on the stream. The docs don't mention this, but it's the standard way of doing it and I checked the source to double check.
Either way: this is definitely harder than it should be, but at least with pipeline
it's harder to get wrong.
Example
I believe the downloadFile function should be defined like this to correctly wait for it to finish, and then close the fd:
async function downloadFile(url, destFilePath) {
let source = await fetch(url).body;
let sink = fs.createWriteStream(destFilePath, { mode: 0o755 });
await pipeline(source, sink);
sink.destroy()
}
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.
We cannot promisify neither .destroy()
, nor .end()
, they are synchronous and don't accept any callbacks. One thing we can do is:
const destFileStream = fs.createWriteStream(path, { mode });
await stream.pipeline(response.body, destFileStream);
return new Promise(resolve => {
destFileStream.on("close", resolve);
stream.destroy();
});
This looks quite hacky...
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.
Hmm, wait, stream.end()
does accept an optional 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.
No stream.end()
accepts a callback that is just attached to 'finish'
event...
I suppose we can use the snippet I wrote higher for now...
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 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.
FYI: I have created an issue on nodejs
repo regarding stream.destroy()
not releasing the file handle immediatly.
nodejs/node#31776
3116: vscode: added error handling to download file streams r=matklad a=Veetaha As a followup for #3092 `ts-nested-error` is mine, it is just [one file worth nothing](https://github.com/Veetaha/ts-nested-error/blob/master/src/nested-error.ts), but let's us inspect original errors Co-authored-by: Veetaha <gerzoh1@gmail.com>
This is my first approach to fix this error, need to double-check this on windows still...
Fixes #3087
Fixes #3090