Re: [whatwg/streams] can streams be transferred via postMessage()? (#276)

Okay, the following is more of a brain-dump, sorry in advance. 😛 

> With code like the following:
> 
> ```js
> writer.write(chunk);
> writer.releaseLock();
> worker.postMessage(writable, [writable]);
> ```

It gets even worse. You can hold on to a promise returned by `writer.write()`, and `await` it *after* you've released the lock and transferred the stream.
```js
let writer = writable.getWriter();
let promise = writer.write(chunk);
writer.releaseLock();
worker.postMessage(writable, [writable]);
await promise; // can happen immediately, or much later
```
However, after we've transferred the writable to another context, there's no way for us to get the result of any of our queued writes.

So... will we need to resolve any pending `write()` promises immediately upon transferring the stream? Or reject them with some sort of "detached" error? Or keep them pending forever? 😕

> For example, `writeAlgorithm` returns a promise. We can't clone or transfer the promise, and we can't synchronously observe its state.

We need to maintain enough bookkeeping as part of creating a cross-realm transform readable/writable, such that we can transfer that bookkeeping to another realm and have it re-create the cross-realm transform readable/writable in exactly the same state.

This state needs to include the state of the _backpressurePromise_. We could manually keep track of it with a boolean _backpressure_ flag as part of the cross-realm transform writable, or we could just synchronously observe the promise's state as Domenic suggested. It's a matter of preference.

One tricky bit is the _chunk_ argument inside _writeAlgorithm_. If we start writing the chunk but we're waiting for backpressure to be relieved, then we have not yet sent a `"chunk"` message. However, I *think* we can get around that if we simply transfer **the entire queue** (i.e. _writable_.[[writableStreamController]].[[queue]])? We only remove a chunk from the queue after the write has completed, so if we transfer the stream before that happens, the transferred stream will still have that chunk at the start of its queue.

I guess this does have an edge case: we might have just sent the `"chunk"` message, but then the stream gets transferred before we can run the "Upon fulfillment of _sinkWritePromise_" steps in `WritableStreamDefaultControllerProcessWrite` to remove the chunk from the queue. In that case, we must *not* re-enqueue the chunk in the new realm. It's probably easiest if we add another "bookkeeping flag" to track if the cross-realm transform writable has actually managed to send the last chunk, so that we can remove it from the queue before we transfer the queue.

One problem with transferring the queue in its entirety though is that it is closely tied to _writable_.[[writeRequests]]. Each entry in [[queue]] must correspond to an entry in [[writeRequests]] (or to the [[inFlightWriteRequest]]). If we add a bunch of chunks to the transferred stream's queue, we could "fix" things by adding an equal number of "dummy" promises to [[writeRequests]]. That way, if the user calls `writer.write()`, the new promise from that call will be linked to the correct chunk in the queue.

...I don't know if I'm explaining this properly in prose. Maybe I should just make a draft PR to try out a few ideas? 😅

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/streams/issues/276#issuecomment-662141356

Received on Tuesday, 21 July 2020 22:35:21 UTC