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

Aborting a stream should wait for pending writes #619

Merged
merged 13 commits into from Dec 5, 2016

Conversation

2 participants
@ricea
Collaborator

ricea commented Nov 22, 2016

This is a work-in-progress for #611.

I haven't updated the standard language to match yet. I would like to get an initial round of feedback on the changes before I do that.

PTAL.

@ricea

This comment has been minimized.

Show comment
Hide comment
@ricea

ricea Dec 2, 2016

Collaborator

I think this is pretty much done, except that WritableStreamRejectUnresolvedPromises is still a terrible name for the abstract operation. There are probably some clarity improvements than can be made once we get some more eyeballs on this.

Collaborator

ricea commented Dec 2, 2016

I think this is pretty much done, except that WritableStreamRejectUnresolvedPromises is still a terrible name for the abstract operation. There are probably some clarity improvements than can be made once we get some more eyeballs on this.

@domenic

domenic approved these changes Dec 3, 2016

LGTM with nits. Thanks so much.

Show outdated Hide outdated index.bs
Show outdated Hide outdated index.bs
Show outdated Hide outdated index.bs
Show outdated Hide outdated index.bs
Show outdated Hide outdated index.bs
Show outdated Hide outdated index.bs
Show outdated Hide outdated reference-implementation/lib/writable-stream.js

ricea added some commits Nov 16, 2016

Make an abort() not abort a sink write()
Previously, if abort() was called while a sink write() was in progress,
the promise returned from WritableStream.write() would be rejected.

This behaviour was surprising, since the rejection would happen even if
the underlying write succeeded.

Change the promise returned by WritableStream.write() to always reflect
the success or failure of the underlyingSink.write() that has started.
Make the write() promise fail if transform() throws
When transform() throws it errors the WritableStream. Previously this
caused write()'s promise to reject, but now write() returns the result
of the underlying sink unchanged.

Pass back rejections from transform() to the caller of write().
Ensure .closed does not not resolve before write()
It is confusing if the .closed promise resolves before the promise
returned by write() (except when releaseLock() is called).

Delay the .closed promise until after the promise resolves for the
ongoing write() in the case when abort() is called.
Ensure that write() promises resolve in order
When rejecting queued write() promises due to an abort(), wait until a
pending sink write() call completes before rejecting the rest.
Use the error from us.write() to error the stream
If writer.abort() was called during during execution of an underlying
write(), and then that write rejected, the .closed, close() and
subsequent write() promises would reject with the TypeError from
abort().

Make them reject with the error from write() instead.
Make write() and close() non-interruptible
The underlying abort() method will now not be called until any pending
underlying write() has finished. If an underlying close() is in progress
then abort() will no longer be called at all.

Other behavioural changes:

* If the underlying operation has started, then the writer's write() and
close() methods will always reflect the result of the underlying operation,
rather than being rejected because abort() was called.

* Consistently with the above, if an underlying operation calls
controller.error() then the writer method will still reflect the result from
the underlying operation.

* If a call to writer.abort() has to wait for an underlying write() or
close() to complete, and that underlying operation rejects, then the
underlying abort() will not be called, and writer.abort() will return the
rejection from the failed operation.
Assert close() is not rejected during operation
It would be a logic error to reject a pending writer.close()
if the underlying sink close() method was in progress. Add an assert to
verify that this doesn't happen.

@ricea ricea merged commit 591a6ed into whatwg:master Dec 5, 2016

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details

@ricea ricea deleted the ricea:in_progress_write branch Dec 5, 2016

ricea added a commit to ricea/streams that referenced this pull request Dec 5, 2016

Do not leak the closed Promise on errored release
Prior to whatwg#619, abort() would reject the closed Promise immediately. Now
it waits for the abort() to complete. This means that there can be a
window when the stream is errored but the closed Promise has not been
rejected. If releaseLock() was called during the window it would
incorrectly create a new closed Promise on the assumption it was
already rejected.

Instead, when an abort() is pending, reject the Promise rather than
creating a new one.

ricea added a commit to ricea/streams that referenced this pull request Dec 6, 2016

Do not leak the closed promise on errored release
Prior to whatwg#619, abort() would reject the closed promise immediately. Now
it waits for queued sink operations to finish. This means that there can
be a window when the stream is errored but the closed promise has not
been rejected. If releaseLock() was called during the window it would
incorrectly create a new closed promise on the assumption it was already
rejected.

Instead, when an abort() is pending, reject the promise rather than
creating a new one.

domenic added a commit that referenced this pull request Dec 8, 2016

Reject the correct writer.closed promise on errored release
Prior to #619, abort() would reject the closed promise immediately. Now it waits for queued sink operations to finish. This means that there can be a window when the stream is errored but the closed promise has not been rejected. If releaseLock() was called during the window it would incorrectly create a new closed promise on the assumption it was already rejected.

Instead, when an abort() is pending, reject the promise rather than creating a new one.

domenic added a commit that referenced this pull request Dec 19, 2016

Do not allow abort to happen when a close is about to happen
As of #619, writer.abort() does not disturb the stream while an underlying-sink write or close operation is ongoing. However, it was still possible to cause both underlying sink abort and close to happen, with code like `writer.close(); writer.abort()`. This was because of the asynchronous way in which the stream's state transitions from "closing" to actually having [[inClose]] set to true.

This addresses that problem by peeking at the queue when abort is called. If the state is [[closing]], and the queue contains no writes, we will not perform the underlying abort, and instead just let the underlying close happen. This counts as a success, so we immediately return a promise fulfilled with undefined.

Fixes #632.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment