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

Abort followed by write on WritableStream may invoke a null strategy algorithm #1331

Open
shannonbooth opened this issue Nov 9, 2024 · 4 comments

Comments

@shannonbooth
Copy link

shannonbooth commented Nov 9, 2024

What is the issue with the Streams Standard?

Consider the following test (simplification of https://github.com/web-platform-tests/wpt/blob/8686b7a6d288d3b2c22b5ddb5a21773619b22b85/streams/writable-streams/aborting.any.js#L57 to remove any infrastructure)

<script>

function test() {
  const ws = new WritableStream();

  setTimeout(() => {
      const writer = ws.getWriter();
      const abortPromise = writer.abort("some error");
      writer.write(1);
  }, 0)
}

test();
</script>

The stack of calls from abort(reason) are:

  1. WritableStreamDefaultWriterAbort
  2. WritableStreamAbort
  3. WritableStreamStartErroring
  4. WritableStreamFinishErroring

Step 12 of WritableStreamFinishErroring will then perform:

  1. Let promise be ! stream.[[controller]].[[AbortSteps]](abortRequest’s reason).

Where [AbortSteps](reason) will clear the write algorithms:

  1. Perform ! WritableStreamDefaultControllerClearAlgorithms(this).

Which includes the strategySizeAlgorithm:

  1. Set controller.[[strategySizeAlgorithm]] to undefined.

Once abort has finished, writer.write(chunk) is invoked.

In WritableStreamDefaultWriterWrite step 4 is to:

  1. Let chunkSize be ! WritableStreamDefaultControllerGetChunkSize(controller, chunk).

Which will invoke the strategySizeAlgorithm:

  1. Let returnValue be the result of performing controller.[[strategySizeAlgorithm]], passing in chunk, and interpreting the result as a completion record.

However, this has been nulled out by the call to WritableStreamDefaultControllerClearAlgorithms earlier, as part of the abort.

The stream is in an errored state, and should throw an exception, but this is only performed on step 9 of WritableStreamDefaultWriterWrite.

Speculatively, I think step 4. of WritableStreamDefaultWriterWrite could be moved to just after step 9 where the erroring status is checked, but I am not sure if that has unintended side effects / observable in some way we care about.

@shannonbooth
Copy link
Author

shannonbooth commented Nov 13, 2024

Unfortunately reordering the step does cause unintended side effects - at least for my case it results in wpt.live/streams/writable-streams/bad-strategies.any.html assertion failure on WritableStreamAddWriteRequest for the stream state. https://streams.spec.whatwg.org/#writable-stream-add-write-request

Falling back to a size of 1 in the getter works for both cases, but doesn't seem ideal.

@domenic
Copy link
Member

domenic commented Nov 21, 2024

I'll try to look into this in more detail later, but have you looked at what the reference implementation does in this case? It passes all the tests, including the one you're simplifying, so either the spec is correct already in some way, or there's a bug in the reference implementation which maybe could inspire us on how to fix the spec.

@shannonbooth
Copy link
Author

It looks like it will catch the null strategy size algorithm exception:

Which seems a bit, off. At least to me doesn't read like editorially that it could be null and it feels like there should be a cleaner way to order the steps, e.g by maybe clearing the algorithms at a different time.

@domenic
Copy link
Member

domenic commented Nov 22, 2024

Ah, yep, overzealous catching of exceptions is precisely one of the ways the reference implementation can be buggy compared to the spec. (In the spec, "performing" a null algorithm is undefined behavior, whereas in JS it's just a catchable exception.)

Thanks for investigating; one of the editors (possibly myself) will work on trying to find a good solution here soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants