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

vscode: fix binary is not functional on windows #3092

Merged
merged 1 commit into from
Feb 11, 2020

Conversation

Veetaha
Copy link
Contributor

@Veetaha Veetaha commented Feb 10, 2020

This is my first approach to fix this error, need to double-check this on windows still...
Fixes #3087
Fixes #3090

@Veetaha Veetaha changed the title vscode: fix binary is not functional on windows [WIP] vscode: fix binary is not functional on windows Feb 10, 2020
@matklad
Copy link
Member

matklad commented Feb 10, 2020

Seems reasonable!

@Veetaha Veetaha changed the title [WIP] vscode: fix binary is not functional on windows vscode: fix binary is not functional on windows Feb 10, 2020
@Veetaha
Copy link
Contributor Author

Veetaha commented Feb 10, 2020

Switched to stream.pipeline(), this should prevent us from making this kind of errors at all
@matklad

@Veetaha Veetaha changed the title vscode: fix binary is not functional on windows [WIP] vscode: fix binary is not functional on windows Feb 10, 2020
@Veetaha
Copy link
Contributor Author

Veetaha commented Feb 10, 2020

Wait, rechecked on windows, and this damn pipeline api doesn't actually work ...

@Veetaha
Copy link
Contributor Author

Veetaha commented Feb 10, 2020

God, I love Node's API. Will revert to the first impl, since it does work...

@Veetaha
Copy link
Contributor Author

Veetaha commented Feb 10, 2020

Now we are ready to merge

@Veetaha Veetaha changed the title [WIP] vscode: fix binary is not functional on windows vscode: fix binary is not functional on windows Feb 10, 2020
@marcogroppo
Copy link
Contributor

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?

@Veetaha
Copy link
Contributor Author

Veetaha commented Feb 11, 2020

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

@matklad
Copy link
Member

matklad commented Feb 11, 2020

bors r+

bors bot added a commit that referenced this pull request Feb 11, 2020
3092: vscode: fix binary is not functional on windows r=matklad a=Veetaha

This is my first approach to fix this error, need to double-check this on windows still...
Fixes #3087
Fixes #3090 

Co-authored-by: Veetaha <gerzoh1@gmail.com>
@bors
Copy link
Contributor

bors bot commented Feb 11, 2020

Build succeeded

  • Rust (macos-latest)
  • Rust (ubuntu-latest)
  • Rust (windows-latest)
  • TypeScript

@bors bors bot merged commit 78ee964 into rust-lang:master Feb 11, 2020
@Veetaha
Copy link
Contributor Author

Veetaha commented Feb 11, 2020

@marcogroppo , I've just checked ECMAScript specs. And found that we do can call reject() more than 1 time, so yes, we can omit the code that ensures that reject is not called repeatedly. just on("close", resolve).on("error", reject) will work. I'll create a PR for that today.
Thank you for the involvement!

.on("error", reject)
.pipe(fs.createWriteStream(destFilePath))
.pipe(fs.createWriteStream(destFilePath).on("close", resolve))
Copy link
Member

@yoshuawuyts yoshuawuyts Feb 12, 2020

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 😅

Copy link
Contributor Author

@Veetaha Veetaha Feb 12, 2020

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?

Copy link
Contributor Author

@Veetaha Veetaha Feb 12, 2020

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!

Copy link
Contributor Author

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...
image

Copy link
Member

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()
}

Copy link
Contributor Author

@Veetaha Veetaha Feb 12, 2020

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...

Copy link
Contributor Author

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...

Copy link
Contributor Author

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...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've switched to stream.pipeline() with .on("close") in #3116. Let's leave it like that for now...
@matklad ?

Copy link
Contributor Author

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

bors bot added a commit that referenced this pull request Feb 13, 2020
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>
@Veetaha Veetaha deleted the fix/ebusy-error branch February 24, 2020 20:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants