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

Update TransformStream API & misc. fixups #519

Merged
merged 29 commits into from
Oct 12, 2016
Merged

Conversation

isonmad
Copy link
Contributor

@isonmad isonmad commented Sep 16, 2016

First pass at issue #518 to align with the other stream APIs. Ended up also addressing #498, #331, and #185.

@isonmad isonmad force-pushed the transform1 branch 9 times, most recently from 40c3948 to da6fba2 Compare September 18, 2016 18:27
isonmad added 11 commits September 19, 2016 02:06
transformStream._error does not exist.

This codepath really needs a test.
Align the TransformStream API to resemble ReadableStream more closely.

Issue whatwg#518.
Align with the WritableStream API.

This resolves issues whatwg#518, whatwg#498, whatwg#331, and whatwg#185.
Return a promise with the same semantics as WritableStream's
underlyingSink.close(). This also resolves an old comment,
as well as issue whatwg#518.

Consequently, calling controller.close() after flush() is
called now throws a TypeError.
@ricea
Copy link
Collaborator

ricea commented Sep 20, 2016

If I understand correctly, this doesn't enforce any ordering guarantees between the start/transform/flush methods. I would like to have the guarantee that transform() is not called until start()'s Promise resolves (and not at all if it rejects), and flush() is not called until transform()'s Promise resolves.

The reason is that if transform() is called before the Promise returned by start() resolves, then a call to enqueue() by transform() could end up happening before before a call to enqueue() by start(). Similarly with transform() and flush().

It's possible to enforce the ordering within the transformer, but that seems likely to cause a steep learning curve as every developer rediscovers the issue.

I can file a separate issue for this if you like.

Copy link
Member

@tyoshino tyoshino left a comment

Choose a reason for hiding this comment

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

Thanks for the patch

}
const flushPromise = PromiseInvokeOrNoop(transformStream._transformer, 'flush', [transformStream._controller]);
// Return a promise that is fulfilled with undefined on success.
return flushPromise.then(() => TransformStreamCloseReadableInternal(transformStream),
Copy link
Member

Choose a reason for hiding this comment

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

What if controller.error() is invoked inside flush() but flush() resolved? I think this path should perform TransformStreamCloseReadableInternal() when the transform stream is neither errored nor closed.

Copy link
Contributor Author

@isonmad isonmad Sep 20, 2016

Choose a reason for hiding this comment

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

You're right, that should be fixed, though I'm tempted to make controller.error() throw after flush() is called, like with controller.close().

Otherwise, what should happen if controller.error() is called with one error, and flush throws or rejects the promise with a different error? What should happen if controller.close() is called, but then the flushPromise is rejected? It seemed simpler to give flush only a single way of signaling success or failure. You already can't signal error() or close() twice after all.

Copy link
Member

Choose a reason for hiding this comment

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

I understand your point. But we could also say that close() becoming disallowed in flush() would make the API a little complicated. Regarding the priority concern, I think we should make the transform stream accept the signal which arrived to it earliest. That said, this kind of rule also complicates the API.

Another question I'm not yet fully sure about is whether the resolution of flushPromise which you've changed to be propagated to writer.close() and readable's final state should always match or not. If close() / error() are allowed inside flush(), we can e.g. close() readable side while making writer.close() rejected. My gut feeling is basically they should match each other, but it seems worth being discussed a bit.

@@ -16,6 +17,17 @@ function TransformStreamCloseReadable(transformStream) {
throw new TypeError('Readable side is already closed');
}

if (transformStream._writableDone === true) {
throw new TypeError('TransformStream is already closing');
}
Copy link
Member

Choose a reason for hiding this comment

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

Did you try to limit the way to close the readable side to only by resolving flush() once flush() is invoked? What's the motivation for that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Having two redundant ways to perform the same action seemed dubious (especially if, as in your other comment, the result is potentially contradictory, instead of redundant). It's already an error to call close() twice, this seemed roughly analogous to that.

@isonmad
Copy link
Contributor Author

isonmad commented Sep 20, 2016

I would like to have the guarantee that transform() is not called until start()'s Promise resolves (and not at all if it rejects), and flush() is not called until transform()'s Promise resolves.

start() doesn't return a promise yet, but adding one with that invariant makes sense. But flush() is only called from sink.close(), and that's already guaranteed not to be called until the last sink.write() promise resolves, which is only resolved when a transform() promise resolves, if I understand it all correctly. There's already an assert(transformStream._transforming === false); before the flush() call to that effect.

isonmad added 5 commits September 21, 2016 00:12
Now all 3 transformer methods are promise-ified.
Disregard previous commit. sink.start and source.start are
called synchronously so this should too. Much simpler.
The conditional is always true, so this always throws.

Is there a point in altering the state of an object when
throwing an exception from its constructor anyway?
This matches behavior for readable and writable streams.
isonmad added 4 commits September 22, 2016 01:37
Per the spec, ReadableStreamDefaultControllerGetDesiredSize
is nothrow. Nothing can go wrong with just adding up numbers.
Before, it was trying to error the readable end, when it
had already errored itself.
@tyoshino
Copy link
Member

Sorry for delay. I'll review this soon.

Copy link
Member

@tyoshino tyoshino left a comment

Choose a reason for hiding this comment

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

lgtm once the 2 comments are addressed. thanks so much.

if (transformStream._transformer.start === undefined) {
return;
}
const p = PromiseInvokeOrNoop(transformStream._transformer, 'transform', [chunk, transformStream._controller]);
Copy link
Member

Choose a reason for hiding this comment

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

s/p/transformPromise/

writer.write('a');
writer.close().then(() => {
t.ok(flushDone, 'flushPromise resolved');
t.end();
Copy link
Member

@tyoshino tyoshino Oct 3, 2016

Choose a reason for hiding this comment

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

unnecessary as you have t.plan()

@tyoshino
Copy link
Member

tyoshino commented Oct 5, 2016

lgtm. thanks!

@tyoshino
Copy link
Member

tyoshino commented Oct 5, 2016

I'd like to add your name to the acknowledgement section of the spec. Do you want to use isonmad or some other name?

I'm going to squash the commits and merge it. If you want to write the commit summary for the squashed commit, please let me know. I can also write it for you if you want.

isonmad added 3 commits October 10, 2016 20:38
This function is just one more abstract operation now
and doesn't need its own separate category.
@domenic
Copy link
Member

domenic commented Oct 10, 2016

Just wanted to say thank you @isonmad for all this work! It's really excellent to get this kind of contribution.

I guess we can merge this now with "isonmad" in the acknowledgments, but do feel free to let us know what would be better!

@isonmad
Copy link
Contributor Author

isonmad commented Oct 10, 2016

That works fine. If it has to all be one monolithic commit, I guess the commit message would be

move TransformStream to promises based api (#519)

Fix a bunch of bugs along the way and add tests for them.

Closes #518, #498, #331, and #185.

@tyoshino tyoshino merged commit a957cc5 into whatwg:master Oct 12, 2016
@tyoshino
Copy link
Member

Thanks so much for your work, @isonmad. I've added your name to the acknowledgement section in 294d720

I'm going to make some follow up commits.

tyoshino added a commit that referenced this pull request Oct 12, 2016
Follow up for #519

As we error the writable stream in TransformStreamErrorInternal, we don't need to take care of the promise returned from the underlying sink write().

transformStream._chunkPending = true;
transformStream._chunk = chunk;

const promise = new Promise(resolve => {
transformStream._resolveWrite = resolve;
transformStream._rejectWrite = resolve;
Copy link
Member

Choose a reason for hiding this comment

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

this should be set to the rejection callback of the promise

if (transformStream._resolveWriter !== undefined) {
transformStream._resolveWriter(undefined);
if (transformStream._rejectWriter !== undefined) {
transformStream._rejectWriter(e);
Copy link
Member

Choose a reason for hiding this comment

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

s/rejectWriter/rejectWrite/

anyway, I just noticed that this is unnecessary since we call _writableController.error() at L118. I'm going to just remove this in #533

tyoshino added a commit that referenced this pull request Oct 12, 2016
Follow up for #519

As we error the writable stream in TransformStreamErrorInternal, we don't need to take care of the promise returned from the underlying sink write().
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

4 participants