-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Description
I am working on a way to make Streams transferrable, ie. make it possible to transfer them between the main page, workers and other contexts with postMessage(). Once a stream has been transferred, objects put into the stream in one context will come out in the other context, meaning that serialisation / deserialisation is performed.
For many use cases, it is critical that this happen efficiently. Specifically, copies must be minimised.
Unlike postMessage(foo, [foo.data]), there is no way to supply out-of-band information to say how an object is to be transferred.
The changes to the Streams Standard are being discussed at whatwg/streams#244, however it will need hooks in the StructuredSerializeWithTransfer() algorithm, and it might be useful to specify a more general mechanism that could be used by other parts of the platform. So I am raising this issue here to discuss the transferring part in isolation.
Here are some options under consideration:
1. Top-level only greedy transfer
If the object itself was an ArrayBuffer, it would be transferred rather than copied. However, if it was { data: ArrayBuffer } then data would be copied, not transferred. This is sufficient for some use cases, but very poor for others. For example, a video frame might contain the frame bitmap as an ArrayBuffer embedded in an object with other metadata. Requiring a copy of the bitmap to be taken would probably render the API unusable for this use case.
2. Deep recursive greedy transfer
The algorithm would transfer any transferable object it found while traversing the object, to any depth. In this case, with { data: ArrayBuffer }, data would be transferred. This is a good match for Stream semantics: putting an object into a stream implies passing ownership. However, there are a few foreseeable problems:
- It may be hard to reuse for other parts of the platform that don't have such clear-cut ownership semantics.
- It would be hard to go back and make the algorithm less greedy in future, since it would mean adding an "opt-out" of greediness to get less greedy behaviour.
- People would inevitably be confused when the object they intended to transfer contained an incidental reference to some other object they didn't intend to transfer.
3. Object transfer meta-protocol
An object would contain metadata indicating how it would like to be transferred. For example, in the above example, data would not be automatically transferred, but changing it to { data: ArrayBuffer, [Symbol.transferKeys]: ['data'] } would cause data to be transferred.
Each transferKeys would only apply to the current level, but it could mark deeper levels that should be recursed into. For example:
{
value1: {
data: ArrayBuffer,
[Symbol.transferKeys]: ['data']
},
value2: {
data: ArrayBuffer,
[Symbol.transferKeys]: ['data']
},
[Symbol.transferKeys]: ['value1']
}Here, value1.data would be transferred, but value2.data would not, because value2 itself was not selected for transfer.
(@domenic pointed out that we can't actually use Symbol.transferKeys here, as Symbol is defined in ECMAScript. But I think for strawman purposes it makes it clear what is going on.)