-
Notifications
You must be signed in to change notification settings - Fork 163
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
Note about underlyingSink close() method is misleading #617
Comments
This made me think that it might make more sense if we change the place of "fallback" from As ricea said,
Agreed. When only |
I think what @tyoshino said makes sense, but an API where I had another idea, which is to replace
This would be a big API change and I am not sure of the benefits. |
I think my preferred solution is that we don't call Right now no part of the streams API has the concept of interrupting an ongoing operation, really. If we were to introduce that, it would probably be based on cancelable promises, and apply more broadly. For example, But for now, without cancel tokens and in general without a concept of canceling an ongoing "atomic" operation, I think it's best to treat each write as atomic, and not something that can be interrupted by abort. What do you think? |
I just realized something else related... I'll keep it in this thread because it reminds me of @ricea's suggestion. Right now, if async write(chunk) {
try {
await fs.write(fd, chunk, 0, chunk.length);
} finally {
await this.close();
}
} (with the code even more complex than this if you don't use async functions and finally.) This seems pretty bad and I'm sad we hadn't realized it before. The guiding goal behind the current setup is roughly that you should only have to implement cleanup logic in one place. That's what guides the fallback in abort. I've never been super-happy with the implementation of that goal as-is, since it leads to strange situations like allowing close + abort, or apparently a bunch of interesting edge cases we're just now discovering. I think @ricea's destroy + shutdown might be too much, because it seems geared toward allowing the interruption of ongoing writes, which as in my previous comment I think we should not really support. But I think it's indeed worth rethinking how to accomplish this goal. One idea would be just |
Since the Having Suppose that we have issued 3
Is this the same as what you had in your mind? |
With this, we gain more precise control over the timing of abort. I'm not sure if this is something we really want. Looks over-killing at a glance. Let me go back to the discussion about need for interrupting an ongoing |
I agree with making us.write() and us.close() non-interruptible. It gains us the guarantee that no two methods on the underlying sink are ever called simultaneously, which is very easy to understand and work with. The temporary loss of functionality is unfortunate, but I think it's acceptable given that we have a clear path towards making write() interruptible again in future. |
I agree this is an important point and analogy with the finally clause makes me also rethink it carefully. @ricea Does your proposal #617 (comment) imply removal of the fallback? It's replaced with allowing developers to make shutdown no-op, and ignore the first argument |
Yes. I expect that implementing only
Actually, Having said that, I'm not seriously pushing my proposal. I think @domenic has provided a good path forward with the existing API, and I don't think it's worth giving it up in exchange for something of doubtful benefit. |
I spoke with @tyoshino in person and here is the plan we came up with:
|
That wasn't quite what I had in mind. Cancel tokens would only be used to control a single write() instance, and wouldn't change the state of the stream. You'd do that separately by calling
I'm glad everyone seems on board with making
This plan sounds good to me, but I am interested in exploring a shutdown/destroy/finally underlying sink API. Maybe we should wait until the dust settles and see how bad things are after resolving this issue and #620 before deciding if that is necessary. |
A minor exception I discovered:
|
Thanks Domenic. I'm fine with decoupling cancellation functionality from the Streams semantics. But does this mean that the WritableStream will take another argument but passes it through to the underlying source without doing anything with it? Or, is it supposed that the value argument will include the cancel token in some way? Just wanna clarify. |
I think this is fine, given the semantics of
Yeah. The thing about cancel tokens is that they represent cooperative cancelation for a given operation. So the stream implementation needs to know if the underlying sink cooperated with the cancelation or not, before it changes any of its state. It does that by looking at the return values of the underlying sink's methods---did they return a canceled promise, or no? If no, then they did not cooperate with the cancelation request, so we should ignore it. It would be a different story if we were passing the cancel token to some operation controlled mostly or entirely by the stream implementation. For example, if we were passing a cancel token to pipeTo(), we could pass that through to any read or write calls and look at how they cooperated, but would we would also interpret a cancelation request on that token to mean "stop piping", so even if an individual write or read does not cooperate because of an uncooperative underlying sink, we could stop the pipe loop as soon as possible. |
This wasn't what I initially though the behaviour should be. But when I saw that Getting into fine details,
I've become confused now. Is |
That sounds like a good idea to me.
The latter was my thinking initially, but when you put it that way, it sounds rather unattractive. I guess we can hand |
It's the same as what I had in my mind. OK. writer.write() taking a cancel token would make the WritableStream/WritableStreamDefaultWriter a bit weird since the interfaces exposed to controllers do nothing with promises, but should be fine. |
This is now implemented in my pull request #619 (feel free to clone that into a whatwg branch if it is easier). Sorry for the delay. I haven't written the standard changes yet, but that should be the easy part. During implementation, I decided that one of the choices I made in my earlier comment #617 (comment) was not that great and changed it. Specifically:
In short, I made |
This was fixed in #619. |
https://streams.spec.whatwg.org/#ref-for-underlying-sink-10
Except if abort() is not defined on the underlyingSink, and user code calls abort(). In that case close() can be called concurrently with write(), possibly wreaking havoc with the assumptions of the author of the underlyingSink.
I would prefer if this note included a qualification like "except when abort() is called, as mentioned below".
close() can also be called while an existing async call to close() is already in progress, but that might be a spec bug.
The text was updated successfully, but these errors were encountered: