HTML and shared memory
You’d think that the HTML Standard would be pretty far removed from shared memory considerations, but as it happens HTML defines a parser for HTML which is intertwined with script execution, defines a way to instantiate new global objects through the iframe
element, defines a way to instantiate new threads (and even processes, depending on the implementation) with workers, and all the various infrastructure pieces that go along with that. Finally, it also defines a message-based communication channel to communicate between those threads and processes.
That still doesn’t give us shared memory. For that, JavaScript needed to evolve and gain a new SharedArrayBuffer
class: a sibling to ArrayBuffer
, with the ability to be accessed from several threads at once. And on top of that we needed to do some work to make it play nicely with all the various globals the web platform provides and make sure it worked with the message-passing system (which you probably know as postMessage()
), all while trying to avoid violating constraints that would make programming with SharedArrayBuffer
objects a nightmare.
We ended up making several changes (and to make sure they all end up being interoperable we wrote accompanying tests):
- Changed the “structured cloning” algorithm into distinct serialization and deserialization algorithms, thereby introducing an intermediate, serialized, form. This was a long overdue refactoring needed to define
MessageChannel
objects properly, since when using those you don't always know at the start where you'll end up. ForSharedArrayBuffer
objects this was critical, since we needed to enforce that they can't be sent across process boundaries. - Redefined the way worker ownership works, so it’s effectively a chain of parent-based ownership rather than all workers being owned by documents. This was necessary as we needed to separate dedicated workers nested in shared workers (not widely supported) from those nested in documents, as memory sharing works differently in these two cases.
- Defined the boundaries between which globals you can share memory. For the record, the web platform has many global objects:
Window
,DedicatedWorkerGlobalScope
,SharedWorkerGlobalScope
,ServiceWorkerGlobalScope
, and soon various subclasses ofWorkletGlobalScope
. A simplified (and slightly inaccurate) description would be that a window can share with any of its same-origin windows iniframe
elements, and any descendant dedicated workers (if there’s no shared/service worker in that chain). A worker (dedicated, shared, or service) can share with any descendant dedicated workers (again, as long as there’s no shared worker in that chain). As worklets aren’t finished yet you’ll have to read up on the actual pull request for the ongoing deliberations. We might post an update when they’re shipping if there’s interest. - Defined a new
messageerror
event that basically ensures that when message-passing goes wrong that error does not get lost. These errors happen when you cannot allocate enough memory in the destination, or try to pass aSharedArrayBuffer
object across a (theoretical) process boundary. As this event is dispatched on the receiving end it’s not the best, but if we detect that libraries often end up passing this information back to the sender we might take care of that at the standards-level at some point. For now messaging errors back was deemed too complicated and not important enough given the conditions under which these occur. - Actually defined how these
SharedArrayBuffer
objects get serialized and then deserialized, how various platform objects integrate with that, and how all the existing APIs that deal with serialization and deserialization in some manner integrate with that. E.g., passingSharedArrayBuffer
objects topushState()
ends up throwing, because we don’t want to store them to disk, butpostMessage()
should generally work (although initial implementations will have limitations here, especially withMessageChannel
).
As always, nothing is perfect and there are some gotchas without a good solution:
- Imagine you have a window with a descendant
iframe
element that has further descendant dedicated workers that all collaborate together with shared memory and then theiframe
element gets navigated. This ends up stopping the workers without the ability to do cleanup. Some workarounds are available, but in general it’s a somewhat fragile setup that deserves a better solution. - Aborting scripts: browsers typically let users abort scripts that are detected to significantly slow down their computer through some heuristic. This can violate some of the invariants the shared memory design tries to provide.
Although the above covers the integration of shared memory into the foundations of the web platform, there is still ongoing work on allowing specific APIs to accept and operate on shared memory. This requires changes to IDL to introduce a mechanism for safelisting APIs that can operate on SharedArrayBuffer
objects, as well as updating specifications to use that new safelisting mechanism, and of course writing tests for these spec changes. This work is still ongoing, but at least now it can build on top of a solid foundation.