| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
There was a problem hiding this comment.
We had a long conversation about swallowing errors after destroy() here, #29197. The consensus was that we should not swallow until after 'close'.
Sorry, something went wrong.
|
@ronag Thinking about this more, the EBADF indicates that we should probably try to avoid this race condition altogether and only call fs.close() until after a fs.read() or fs.write() operation has finished … I’ll update the PR along those lines |
Sorry, something went wrong.
Sorry, something went wrong.
There was a problem hiding this comment.
LGTM
Sorry, something went wrong.
|
Should we apply the same fixes to net.Socket? |
Sorry, something went wrong.
There was a problem hiding this comment.
I think this might need to be a cb(new ERR_STREAM_DESTROYED('write'))
Sorry, something went wrong.
There was a problem hiding this comment.
Why? There hasn’t been any error, has there?
Sorry, something went wrong.
There was a problem hiding this comment.
The write hasn't completed. If we don't send an error here the caller would think the write has completed even though it hasn't.
See, https://github.com/nodejs/node/blob/master/lib/_stream_writable.js#L821
Sorry, something went wrong.
There was a problem hiding this comment.
I’m worried that introducing an error when there previously was none (or at least not usually) would be semver-major, and I’d prefer to keep this PR as close to just being a fix for the bug as possible.
Sorry, something went wrong.
There was a problem hiding this comment.
It's only if called with a callback (which is very unusual) in which case it's actually a bug if it's not an error.
Though if you are worried about it I guess we can leave it as is. It's an unusual edge case after all. Could we at least have a separate semver-major PR for "correct" behaviour?
Sorry, something went wrong.
There was a problem hiding this comment.
Since this is a correctness issue I don't think it needs to be semver-major?
Maybe it doesn’t need to be, but I still feel like these are two very different things…
I’d rather not mix the two, and I think we’ve treated other situations where we add errors for consistency as semver-major in the past (and I don’t really see any reason not to do that here, too).
Sorry, something went wrong.
There was a problem hiding this comment.
I don't think that follows, does it? Right now you'll receive an EBADF error event. While the EBADF error is, er, in error, it at least tells you that the write didn't go through.
(I suppose it could also end up writing to a different file if the fd was reopened in the mean time, which of course is - edit: a lot - worse than what this PR does.)
Sorry, something went wrong.
There was a problem hiding this comment.
I guess my reasoning is mostly that this is only relevant when the stream has already been destroyed at this point, and so it should be expected that writes may not finish?
If you feel strongly, I’ll apply @ronag’s suggestion, but I’m still a bit worried about breakage.
Sorry, something went wrong.
There was a problem hiding this comment.
The way I see it is that you already receive unpredictable EBADF errors now. A predictable error is better, and certainly better than silently dropping data on the floor.
Another way of looking at it: how likely is this change to break existing, functionally correct code? I expect the answer is 'close to zero' - any code that breaks was probably already broken, just not reliably so.
Does that sound reasonable?
Sorry, something went wrong.
There was a problem hiding this comment.
I’ve pushed a commit with the suggestion … still feeling a bit worried about it but we’ll see if this is problematic
Sorry, something went wrong.
_write and _read can be called from 'connect' after Socket.destroy() has been called. This should be a noop. Refs: nodejs#30837
There was a problem hiding this comment.
I share @ronag's concern though: it's tantamount to silently ignoring the write request from the user.
Since this is a correctness issue I don't think it needs to be semver-major?
Sorry, something went wrong.
There was a problem hiding this comment.
This is observable when emit() is monkey-patched, which isn't entirely uncommon. Not a reason per se not to introduce this pattern (it's pretty elegant) but I thought I'd point it out anyway.
Sorry, something went wrong.
|
Just a thought. What if for whatever reason the io doesn’t complete? Do we need a timeout? Or does libuv handle that? |
Sorry, something went wrong.
|
@ronag In that case, this PR delays the close() call along with it. I don’t think that’s a bad thing, though. |
Sorry, something went wrong.
|
I would like to ask for @mcollina's take on this before merging. See, #30864 (comment). |
Sorry, something went wrong.
Sorry, something went wrong.
Part of the flakiness in the parallel/test-readline-async-iterators-destroy test comes from fs streams starting `_read()` and `_destroy()` without waiting for the other to finish, which can lead to the `fs.read()` call resulting in `EBADF` if timing is bad. Fix this by synchronizing write and read operations with `close()`. Refs: nodejs#30660
Sorry, something went wrong.
|
@ronag I guess we can do that, but at the same time I think we should fix this bug. |
Sorry, something went wrong.
Given @mcollina's answer in the linked comment I'm not sure he would agree with this PR. Another valid (?) solution is also to just add on('error') handlers in the failing test. |
Sorry, something went wrong.
|
@ronag Let’s wait for him to comment. In my opinion EBADF just because of using async iterators over a file stream is very clearly a bug. |
Sorry, something went wrong.
Sorry, something went wrong.
|
I mostly agree. My personal concern is whether this fix should apply everywhere (net, http, http2, quic etc...) or whether fs is a somehow an edge case?
This is a bit strange for me though. Shouldn't the iterator be released once leaving the for block? How can a released async iterator cause an exception at destroy()? @mcollina @benjamingr what is the expect semantics of async iterators here? https://github.com/nodejs/node/blob/master/test/parallel/test-readline-async-iterators-destroy.js I would expect the iterator to be released and release any error listeners. The readable stream will of course still 'error' (which might or might not make sense) and an error listener should be registered regardless? |
Sorry, something went wrong.
|
I'm a bit conflicted by this change. I think we should consider making the callback of destroy(err, cb) documented and part of the official API. What do you think? |
Sorry, something went wrong.
There was a problem hiding this comment.
LGTM
Sorry, something went wrong.
Yes, that's a bit contradictory. I guess it is "safe" to wait for I/O if we're under the assumption that it will always complete (or fail) within reasonable time, otherwise we might end up with a stuck stream without means to abort it, e.g. a socket trying to write to a server which is bugged/crashed/corrupt? It might be a case by case basis. In fs I would guess it's very unlikely it would not complete within reasonable time (if you exclude FUSE). Also depends on whether e.g. libuv or the os has some sort of timeout or error handling for this.
I think it should be part of the official API. Not sure how that helps us here though? Also, before making it public we should probably ensure the cb is invoked asynchronously (which is not the case today). |
Sorry, something went wrong.
I agree.
I think documenting that is enough. |
Sorry, something went wrong.
I’m not sure if that’s always the case, but here it’s definitely problematic. Getting EBADF is bad enough but I think it’s even possible that data is read or written from the wrong file here if the race condition timing works out really badly.
I’d be okay with that, yes 👍 |
Sorry, something went wrong.
Part of the flakiness in the parallel/test-readline-async-iterators-destroy test comes from fs streams starting `_read()` and `_destroy()` without waiting for the other to finish, which can lead to the `fs.read()` call resulting in `EBADF` if timing is bad. Fix this by synchronizing write and read operations with `close()`. Refs: #30660 PR-URL: #30837 Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
Part of the flakiness in the parallel/test-readline-async-iterators-destroy test comes from fs streams starting `_read()` and `_destroy()` without waiting for the other to finish, which can lead to the `fs.read()` call resulting in `EBADF` if timing is bad. Fix this by synchronizing write and read operations with `close()`. Refs: #30660 PR-URL: #30837 Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
Part of the flakiness in the parallel/test-readline-async-iterators-destroy test comes from fs streams starting `_read()` and `_destroy()` without waiting for the other to finish, which can lead to the `fs.read()` call resulting in `EBADF` if timing is bad. Fix this by synchronizing write and read operations with `close()`. Refs: #30660 PR-URL: #30837 Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
Part of the flakiness in the parallel/test-readline-async-iterators-destroy test comes from fs streams starting `_read()` and `_destroy()` without waiting for the other to finish, which can lead to the `fs.read()` call resulting in `EBADF` if timing is bad. Fix this by synchronizing write and read operations with `close()`. Refs: #30660 PR-URL: #30837 Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
| Back | FazBrowse Home | New Git URL |
Part of the flakiness in the
parallel/test-readline-async-iterators-destroy test comes from
fs streams starting _read() and _destroy() without waiting
for the other to finish, which can lead to the fs.read() call
resulting in EBADF if timing is bad.
Fix this by synchronizing write and read operations with close().
Refs: #30660
/cc @ronag
Checklist