Open Bug 1599542 Opened 5 years ago Updated 2 years ago

Support upgrading a SameProcessDifferentThread structured clone buffer to a DifferentProcess buffer

Categories

(Core :: JavaScript Engine, task, P2)

task

Tracking

()

People

(Reporter: asuth, Unassigned)

References

(Blocks 1 open bug)

Details

The current JS structured clone implementation assumes that we can know at serialization time where the resulting message will be deserialized. This has historically been true for Window.postMessage and will always be true for Worker.postMessage and is sufficiently true for BroadcastChannel.postMessage. But for MessagePort.postMessage, ServiceWorker.postMessage, and Client.postMessage we've used the lowest common denominator of DifferentProcess.

The cost of using DifferentProcess has been that there is never any performance benefit to transferring an ArrayBuffer (which has not gone un-noticed, see bug 1587752). This is now a logistical problem for SharedArrayBuffer support because serialization throws if you try and serialize a SharedArrayBuffer via DifferentProcess.

MessagePorts have the frustrating super-power that they can be transferred at any time, even after start() has been called and they are actively delivering messages. This means that it's possible for port.postMessage() to be called at a time when the other end of the channel is known to be entangled in another origin and another process, but by the time the message event is (attempted to be) dispatched, the port has been shipped back to the same-process and same-origin as its author (although potentially on another thread). It's also the case that a port that looks like the message will be delivered to the same-process will end up actually being delivered to a different process.

Looking at the JS structured clone implementation, there's not actually much difference between SameProcessDifferentThread and DifferentProcess. The big difference is how transferring of array buffers happens.

It seems feasible to introduce an enhancement that lets us upgrade from SameProcessDifferentThread to DifferentProcess by serializing the contents of any detached array buffers into the serialization stream and freeing the underlying buffers. The reverse operation wouldn't be needed.

The other alternative would be to let the case of arraybuffer serialization be handled by the hooks so that the DOM StructuredCloneHolder mechanism could assume responsibility for the buffers.

A related enhancement that might want to be folded in is having the structured clone process indicate if a SharedArrayBuffer (or other shared primitive) was serialized in the buffer. In this case, the MessagePort implementation would never let the buffer leave the process and instead ship an IOU/token stand-in for the message payload to handle the case where the message ends up same-process again, or be dispatched as a "messageerror" otherwise.

I'll be updating bug 1587752 with additional details with a proposal that builds on this.

Priority: -- → P2

(In reply to Andrew Sutherland [:asuth] (he/him) from comment #0)

It seems feasible to introduce an enhancement that lets us upgrade from SameProcessDifferentThread to DifferentProcess by serializing the contents of any detached array buffers into the serialization stream and freeing the underlying buffers. The reverse operation wouldn't be needed.

We need to be careful about timing. Example: I'm serializing, I store an ArrayBuffer, then during a callback for serializing something else, I modify the contents of the ArrayBuffer. The spec says the receiver should get the modified data.

When we serialize Transferable data, we have to do it at the end after all callbacks are complete. And since we use mostly a linear byte buffer for this stuff, we have to write this data at the end of the stream.

The other alternative would be to let the case of arraybuffer serialization be handled by the hooks so that the DOM StructuredCloneHolder mechanism could assume responsibility for the buffers.

A related enhancement that might want to be folded in is having the structured clone process indicate if a SharedArrayBuffer (or other shared primitive) was serialized in the buffer. In this case, the MessagePort implementation would never let the buffer leave the process and instead ship an IOU/token stand-in for the message payload to handle the case where the message ends up same-process again, or be dispatched as a "messageerror" otherwise.

I'll be updating bug 1587752 with additional details with a proposal that builds on this.

What does the spec say about the behavior of transferred SharedArrayBuffers? If you send one through a MessagePort that initially looks like it goes to a different process, but gets changed to point to the same process, does the recipient get shared data or not?

Right now, it sounds to me like there are two different things here, and there may not be much shared between the work done for each (?). One is the (regular) ArrayBuffer transferring optimizations, to avoid copies when we end up deserializing a transferred ArrayBuffer in the same process that sent it. The other is SharedArrayBuffer cloning (NOT transferring, which according to current spec is an error), which it sounds like we need to support the DifferentProcess case as well as the "initially looked like DifferentProcess but ended up SameProcess{Same,Different}Thread" case.

Is that correct?

(In reply to Steve Fink [:sfink] [:s:] from comment #1)

We need to be careful about timing. Example: I'm serializing, I store an ArrayBuffer, then during a callback for serializing something else, I modify the contents of the ArrayBuffer. The spec says the receiver should get the modified data.

Right, the detaching happens at the end of the algorithm, after the serialization changes. That wouldn't change. It's just a question of being able to convert the detached buffers into something that can be serialized via IPC. Since we already send other data alongside the serialized bytestream, the upgrade process need not be entirely re-writing the bytestream.

One option is that the transferred arrays could always be handled via the DOM support code which could store an array of UniquePtr's in its in-memory representation and write those out like normal arrays over IPC that become UniquePtr's again on the other side.

But I don't think this can be done with the existing code because the JS code only lets the DOM support code try to serialize things JS doesn't already know how to serialize. One could perhaps add an extra set of callbacks to JSStructuredCloneCallbacks for arraybuffers/similar that by default maintains the existing status quo, but lets the DOM DifferentProcess mode still potentially be efficient if the data never leaves the process? Right now there are custom transfer callbacks that are used but only in the else branch of if (cls == ESClass::ArrayBuffer)

It might make the sense for the hook to be at the buffer level rather than exposing objects.

What does the spec say about the behavior of transferred SharedArrayBuffers? If you send one through a MessagePort that initially looks like it goes to a different process, but gets changed to point to the same process, does the recipient get shared data or not?

Yes, the recipient gets the shared data. (And for clarity, noting that your use of transfer here is not ownership transferring, consistent with your point below.)

Right now, it sounds to me like there are two different things here, and there may not be much shared between the work done for each (?). One is the (regular) ArrayBuffer transferring optimizations, to avoid copies when we end up deserializing a transferred ArrayBuffer in the same process that sent it. The other is SharedArrayBuffer cloning (NOT transferring, which according to current spec is an error), which it sounds like we need to support the DifferentProcess case as well as the "initially looked like DifferentProcess but ended up SameProcess{Same,Different}Thread" case.

Yes, the core primitives are:

  1. Be able to effectively convert from SameProcessDifferentThread to DifferentProcess.
  2. Be able to know if there's a SharedArrayBuffer in the SameProcessDifferentThread serialized representation.

The primitives are somewhat related in that:

  • The upgrade probably needs to know if there's a SAB in the serialized clone so it can refuse to upgrade.
  • The postMessage implementation enhancement I have not yet written up in bug 1587752 would perform an optimization if it knows there's a SAB in the clone. Specifically, if there's a SAB in the clone, it would never upgrade SameProcessDifferentThread to DifferentProcess and it would never send the clone data to the other process. Just an actor/token that's only redeemable in the current process. If we go to perform the deserialization in a different process, we know we'll just be generating a "messageerror" event instead of a "message".

See also: Bug 1605566 addressed the SharedArrayBuffer and WASM cases by introducing RefMessageData.

See Also: → 1605566
Severity: normal normal → S3 S3
You need to log in before you can comment on or make changes to this bug.