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.
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
MessageChannelobjects properly, since when using those you don't always know at the start where you'll end up. For
SharedArrayBufferobjects 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:
ServiceWorkerGlobalScope, and soon various subclasses of
WorkletGlobalScope. A simplified (and slightly inaccurate) description would be that a window can share with any of its same-origin windows in
iframeelements, 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
messageerrorevent 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 a
SharedArrayBufferobject 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
SharedArrayBufferobjects 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., passing
pushState()ends up throwing, because we don’t want to store them to disk, but
postMessage()should generally work (although initial implementations will have limitations here, especially with
As always, nothing is perfect and there are some gotchas without a good solution:
- Imagine you have a window with a descendant
iframeelement that has further descendant dedicated workers that all collaborate together with shared memory and then the
iframeelement 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.