New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.

Issue 659037 link

Starred by 10 users

Issue metadata

Status: Fixed
Owner:
Last visit > 30 days ago
Closed: Feb 2018
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: All
Pri: 2
Type: Bug

Blocking:
issue 809995


Show other hotlists

Hotlists containing this issue:
Hotlist-1


Sign in to add a comment

Optimize cross-process transfer of ArrayBuffer over MessagePort

Project Member Reported by nhiroki@chromium.org, Oct 25 2016

Issue description

Currently, sending transferable ArrayBuffer over MessagePort/ServiceWorker/ServiceWorkerClient is supported by copy-and-neuter semantics[1] (not move semantics) and copies an internal buffer at least 4 times on serialization/deserialization and writing/reading an IPC message. As our proposals [2, 3] , we could avoid 2 copies by tweaking serialization format.

[1] https://bugs.chromium.org/p/chromium/issues/detail?id=334408#c38
[2] https://docs.google.com/document/d/17o_cjtc3Gk5cDZhoUc37EyhhvBb4wMlCROJvoC2Aokg/edit#heading=h.mkg6dtcx3iyv
[3] https://docs.google.com/document/d/17o_cjtc3Gk5cDZhoUc37EyhhvBb4wMlCROJvoC2Aokg/edit#heading=h.vchusxl6tnsc
 

Comment 1 by darin@chromium.org, Mar 15 2017

Cc: haraken@chromium.org roc...@chromium.org
Owner: darin@chromium.org
Now that MessagePort is based on a mojo::MessagePipeHandle, it would be possible to transfer an ArrayBuffer across processes if we backed the ArrayBuffer with a mojo::SharedBufferHandle. (Caveat: probably only want to allocate SHM for "large" ArrayBuffer objects.)

I'm going to look into this a bit further...

Comment 2 by binji@chromium.org, Jul 5 2017

Another (somewhat) related issue is sharing SharedArrayBuffer over MessagePort. This should only be required for in-process sharing, between threads. The primary issue is that a message is always serialized in blink::MessagePort::postMessage, which loses the attached ArrayBufferContents stored in the SerializedScriptValue. Any thoughts as to the best way to implement this?
#2: If it's restricted to the same process (spec-ese is something like "site instance", I think), it might not be too bad (we can come up with a way to tell if a message came from the same process). If we want to do that cross-process, it's kinda hard to imagine how to do it without slowing down typical uses (e.g. putting all SharedArrayBuffers in SHM, or making all threads pause use of a SAB while we copy it to SHM).

Comment 4 by mek@chromium.org, Jul 5 2017

I think the problems with SharedArrayBuffer go much deeper than figuring out how to avoid serialization. I.e. the spec as currently written seems to require postMessage to work correctly as long as whoever ultimately ends up deserializing the SharedArrayBuffer is in the same agent cluster as whoever serialized the buffer. But because of MessagePorts annoying flexibility there is no guarantee that the message doesn't have to pass through some other process/agent cluster on its way to the final destination.

For example document1 can create a MessageChannel, post a SharedArrayBuffer to one port, and pass the other port to some other document. The other document can then pass the port through arbitrarily many other documents/workers/contexts/agents/agent clusters, eventually passing the same port back to document1. According to the spec, document1 should then be able to attach a message event handler and correctly receive the posted buffer. 

Probably the only way to implement this is to keep some kind of per-process map of in-queue SharedArrayBuffers, serialized as some ID in the message. That way on deserialization you can check if the buffer was serialized in the same process/agent cluster, and properly deserialize it. You might have to also add a message pipe or something to the serialized message (at the mojo level at least) to be able to detect a reference to your shared array buffer from a queued message going away in a different process (not sure how else you would know when to clean up your bookkeeping table of shared array buffers in transit).

Either way, you definitely can't ignore cross-process message queueing if you want to actually implement the behavior as spec'ed.
Cc: brada@google.com malcolmwhite@google.com binji@chromium.org
Cc: -brada@google.com
Cc: bradnelson@chromium.org
This has now become a little more interesting for the SharedArrayBuffer case, as WebAudioWorklets that want to access a SharedArrayBuffer will need it to arrive over a MessagePort.

Comment 9 by darin@chromium.org, Jul 28 2017

@brad - can you point me at the spec. from https://github.com/WebAudio/web-audio-api/wiki/AudioWorklet-IDL-Proposal, it looks like this works more like a dedicated web worker, where there is a postMessage and onmessage methods on the global scope. i don't see where MessagePort comes in. if it is just like web worker, then we can use the same optimizations as we'll know that the audio worklet is in the same process.
Cc: rtoy@chromium.org hongchan@chromium.org
@darin - The AudioWorklet spec has been in high flux, seems like the interface changes each I've checked up on it. :-/

I believe the latest interface uses a message port in place of letting you have an onmessage handler:
https://webaudio.github.io/web-audio-api/#AudioWorkletNode

An example:
https://webaudio.github.io/web-audio-api/#vu-meter-node

As discussed offline with bradnelson@, Mojo message pipes now support transfer of opaque, unserialized message objects with custom lazy serialization routines. So you could in theory support something *like* this with MessagePort today.

But as mek@ points out in comment #4, it sounds like the spec is too strict for this to be a valid solution: there is no guarantee that a MessagePort isn't bounced to another process before returning to the same process as its peer; in that case there can be a small transient window wherein the underlying pipe is still proxying through the intermediate process, and in violation of the spec we could end up serializing a message sent between the ports within that window.

Of course as a practical matter I don't think that would be too problematic: it just means in rare cases the occasional message transmission is not as efficient as it could be, so you could argue that maybe the behavior is a tad over-specified.

Still, the proposal to have some kind of process-wide buffer ID map seems reasonable too.
@darin To clarify how MessagePort in AudioWorklet is different from a dedicated web worker:

- The main global scope instantiates AudioWorkletNode out of AudioContext when user requests.
- Then AudioWorkletProcessor will be constructed in AudioWorkletGlobalScope as a counterpart of AudioWorkletNode.
- This pair of AudioWorkletNode and AudioWorkletProcessor uses MessagePort in each instance for bi-directional communication.
- User can create multiple pairs of AudioWorkletNode/Processor in a single scope.

The working group reached this design through a long discussion:
https://github.com/WebAudio/web-audio-api/issues/1194

Comment 13 by darin@chromium.org, Jul 31 2017

@hongchan - got it. seems like there is some unfortunate complexity here. i don't know how valuable it is to be using MessagePort here versus onmessage / postMessage from the global scope.
Cc: ikilpatrick@chromium.org
Oh, here the issue is "cross-process" transfer, not "cross-thread" on. The ThreadedWorklet (audio and animation worklet) do cross-thread operations, not cross-process. I am not really sure why AudioWorklet is brought up in this discussion.

@hongchan - Currently we are unable to pass SharedArrayBuffers over MessagePort (for the same underlying reason ArrayBuffer transfers are serialized). As rtoy + you + I have talked about, getting a SAB to the worklet would allow background audio threads for audio engines that use this.
SharedArrayBuffers are spec-ed such that they can't cross a process boundary, but with a MessagePort you can't tell from the sending side if the receiver is in the same process. Fixing this hasn't been a high priority, but with AudioWorklet coming, we might care sooner.

I have a 20%er who's expressed interest in getting this working (SABs over MessagePort), but wanted to make sure there's agreement on the right way to do this, and make sure it's not too much to bite off.

rockot's suggestion sounds appealing, aside from being racy while one of the MessagePort end points is in transit.


If I'm understanding correctly, the complexity is around doing a structured clone of an SAB. If however the SAB is included in the transfer list, then there should be no problem, right? In other words, port.postMessage(sab, [sab]) should be straightforward to implement as "sab" becomes an invalid reference immediately after calling postMessage in this way. Am I missing something?
@bradnelson I had the exact same thought with @darin. I naively thought that introducing MessagePort to AudioWorkletNode/Processor will allow us to pass SAB between the pair.

> SharedArrayBuffers are spec-ed such that they can't cross a process boundary, but with a MessagePort you can't tell from the sending side if the receiver is in the same process.

So does this mean a spec change for SAB? Or MessagePort? Or only the implementation? I am not familiar with the area and would like to know more about this. Because this is important for game developers who want to use AudioWorklet for their audio engine.
@darin: SABs are spec'd as being non-transferable objects, so they can't be in the transfer list. They also can't be neutered (new terminology is detached).
@binji - that seems pretty unfortunate. is there a good reason for that restriction? passing SHM over a pipe is a normal concept in operating systems.
SABs in the transfer list was originally discussed here: https://github.com/tc39/ecmascript_sharedmem/issues/98

My summary of the discussion: transferring implies detaching, so you shouldn't put the SAB in the transfer list to share it with a Worker. Instead, you should postMessage it normally to share, similar to a Blob.

I don't remember when we decided that SABs should be limited to being shared within a process, but I believe it was a fairly old decision, and primarily made for simplicity of implementation, and because the primary use cases were for sharing memory between threads, not processes. I believe this limitation could be relaxed with the current design, but I don't know if anyone has looked into this.
#20: It would presumably imply that we'd have to allocate SharedArrayBuffer memory as OS-level shm, which AFAIK we don't presently do. (Right?)
@jbroman - In fact, I think we would use Mojo SHM (https://cs.chromium.org/chromium/src/mojo/public/c/system/buffer.h) so that it is easy to transfer or share (i.e., duplicate and transfer) across a mojo message pipe. IMO, we should do this for large AB and large SAB. See comment #1.
@binji - we should think of passing messages over a MessagePort as passing messages potentially across process boundaries (e.g., to a Shared Worker) or think of OOPIF where different iframes may be in different processes.

Comment 24 by binji@google.com, Aug 24 2017

@jbroman: right, SABs are currently allocated using the same ArrayBuffer::Allocator as non-shared ArrayBuffers. It could be extended however; recently the allocator was extended to allow for reserving regions and changing the memory protection (for WebAssembly trap-handling on OoB accesses).

It may not be necessary if we always know that we want to allocate shared and non-shared ArrayBuffers w/ Mojo SHM, though.

@darin: Makes sense. In case you haven't seen it, this doc (seems to be Google internal only) talks about this topic, in particular w.r.t. a potential race condition: https://docs.google.com/a/google.com/document/d/1hCjChKid7eWqO3GpUl9n07O4ynj7xjyV5sHasNaJAaI/edit?usp=sharing
Now that MessagePort is using mojo with lazy serialization, we should be able to transfer ArrayBuffers (and ImageBitmaps) within a process without copying memory. I'm taking a look at this.
Cc: malcolmwhite@chromium.org
 Issue 809995  has been merged into this issue.
Project Member

Comment 27 by bugdroid1@chromium.org, Feb 16 2018

The following revision refers to this bug:
  https://chromium.googlesource.com/chromium/src.git/+/b58dfe2d8d069a52cf85ed78f946a6f6bd969387

commit b58dfe2d8d069a52cf85ed78f946a6f6bd969387
Author: Malcolm White <malcolmwhite@google.com>
Date: Fri Feb 16 19:22:51 2018

Transfer ArrayBuffers and ImageBitmaps over mojo for MessageChannel

This change eliminates memory copies when mojo determines that the
destination port is within the sender's process. With lazy
serialization in place, ArrayBuffers and ImageBitmaps are not actually
serialized if the ports are in the same process.

This is essentially Option #2 in https://docs.google.com/document/d/17o_cjtc3Gk5cDZhoUc37EyhhvBb4wMlCROJvoC2Aokg/edit#heading=h.cyzhzcigg93g

BUG= chromium:659037 

Change-Id: Iab2b0d4708b3289e953fc84d2ddd01341bfc2d2e
Reviewed-on: https://chromium-review.googlesource.com/854240
Commit-Queue: Malcolm White <malcolmwhite@google.com>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Bo <boliu@chromium.org>
Reviewed-by: Kentaro Hara <haraken@chromium.org>
Reviewed-by: Kinuko Yasuda <kinuko@chromium.org>
Reviewed-by: Marijn Kruisselbrink <mek@chromium.org>
Cr-Commit-Position: refs/heads/master@{#537374}
[modify] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/content/public/android/java/src/org/chromium/content/browser/AppWebMessagePort.java
[modify] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/third_party/WebKit/Source/bindings/core/v8/serialization/SerializedScriptValue.h
[modify] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/third_party/WebKit/Source/core/BUILD.gn
[modify] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/third_party/WebKit/Source/core/DEPS
[modify] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/third_party/WebKit/Source/core/messaging/BlinkCloneableMessageStructTraits.h
[modify] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/third_party/WebKit/Source/core/messaging/BlinkTransferableMessage.typemap
[modify] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/third_party/WebKit/Source/core/messaging/BlinkTransferableMessageStructTraits.cpp
[modify] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/third_party/WebKit/Source/core/messaging/BlinkTransferableMessageStructTraits.h
[add] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/third_party/WebKit/Source/core/messaging/BlinkTransferableMessageStructTraitsTest.cpp
[modify] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/third_party/WebKit/Source/core/messaging/MessagePort.h
[modify] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/third_party/WebKit/common/BUILD.gn
[modify] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/third_party/WebKit/common/DEPS
[add] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/third_party/WebKit/common/array_buffer/OWNERS
[add] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/third_party/WebKit/common/array_buffer/array_buffer_contents.mojom
[modify] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/third_party/WebKit/common/message_port/message_port.mojom
[modify] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/third_party/WebKit/common/message_port/transferable_message.typemap
[modify] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/third_party/WebKit/common/message_port/transferable_message_struct_traits.cc
[modify] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/third_party/WebKit/common/message_port/transferable_message_struct_traits.h
[modify] https://crrev.com/b58dfe2d8d069a52cf85ed78f946a6f6bd969387/third_party/WebKit/public/common/message_port/transferable_message.h

Blocking: 809995
Status: Fixed (was: Available)

Sign in to add a comment