Programming Servo: A generic “worker event-loop”

Let’s take a look at how in Rust you can have an algorithm generic over T, where T is further bound by a trait, which itself is generic over one of several parameters.


A little introduction to workers and their event-loops in the web

The HTML living standard tells us the following with regards to the event-loops of an user agent:

To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. There are two kinds of event loops: those for browsing contexts, and those for workers.
(https://html.spec.whatwg.org/multipage/#event-loops)

A bit more on the worker event-loops:

Each WorkerGlobalScope object has a distinct event loop, separate from those used by units of related similar-origin browsing contexts. This event loop has no associated browsing context, and its task queues only have events, callbacks, and networking activity as tasks. These event loops are created by the run a worker algorithm.
https://html.spec.whatwg.org/multipage/#worker-event-loop

Furthermore, there are several kinds of workers:

There are two kinds of workers; dedicated workers, and shared workers.
https://html.spec.whatwg.org/multipage/#infrastructure-2

And the W3c also defines a “service worker.”

Lastly, the living standard also writes that “The global scope is the “inside” of a worker”.


Workers in Servo

As a result of the above, Servo ended-up with several worker “global scopes”, each with their own “run the event-loop” algorithm. While working on a related issue, I realized that I was often making the same changes in both of those worker event-loops, which was error prone. So I set to out to turn that code into one “generic worker event-loop”.

The ‘run_event_loop’ of the ServiceWorkerGlobalScope at https://github.com/servo/servo/blob/029715aba6f4a4c62b5a2759d47dda57954cc433/components/script/dom/serviceworkerglobalscope.rs
The ‘run_event_loop’ of the DedicatedWorkerGlobalScope at https://github.com/servo/servo/blob/029715aba6f4a4c62b5a2759d47dda57954cc433/components/script/dom/dedicatedworkerglobalscope.rs

This proved harder then I though, because what I needed was a run_worker_event_loop<T> sort of algorithm, where T was a type of “Worker Globalscope” and a replacement for &self. But since &self was previously used to access some capabilities of the “Worker Globalscope”, I would need T to implement a trait giving run_worker_event_loop access to these capabilities of T .

Yet those capabilities themselves required to be generic because each event-loop consisted of handling different messages on different channels, and then handling those messages as different type of event-loop specific events.

Furthermore, those run_event_loop differed in some ways, for example because the “dedicated worker global scope” takes this worker argument, and handles it as part of each iteration of the event-loop, whereas the “service worker global scope” lacks that concept.

The different stuff inside each type of “worker globalscope”

At first, I tried to have some sort of “double generic” setup looking like run_worker_event_loop<T<TimerMsg, WorkerMsg, Event>> , where I wanted to express something like run_worker_event_loop is generic over T, which itself is generic over <TimerMsg, WorkerMsg, Event> , and the Rust compiler didn’t seem to appreciate that approach a lot.

In the end, the solution came in the form of a run_worker_event_loop<T, TimerMsg, WorkerMsg, Event>, where TimerMsg, WorkerMsg, Eventare arguments passed on to the underlying trait bound on T: WorkerEventLoopMethods<TimerMsg = TimerMsg, WorkerMsg = WorkerMsg, Event = Event>. Let’s take a look into what that exactly means…


A trait

So our “worker global scope” T, will have to implement WorkerEventLoopMethods .

https://github.com/servo/servo/commit/029715aba6f4a4c62b5a2759d47dda57954cc433#diff-38925b1ff4f20bfb4b257eb7a6e973d8R79

The trait itself is generic with regards to three “associated typesTimerMsg, WorkerMsg, Event .

This gives the generic event-loop a few ‘hooks’ to access the specific capabilities of each “worker global scope”, which are needed for the parts of the event-loop that are specific to the “worker global scope” at hand.

Those ‘hooks’ themselves require a few generic parameters, because each “worker global scope” receives different types of messages, and expects different types of events resulting from those.

Here is what an implementation looks like:

https://github.com/servo/servo/commit/029715aba6f4a4c62b5a2759d47dda57954cc433#diff-c3210c2a1bf5e1de39588154cd766100R159

As you can see, all that the implementation does is “make available” that which is specific to the DedicatedWorkerGlobalScope , it doesn’t contain any event-loop logic, just the stuff that the generic event-loop will need to specific it’s behavior for a given “worker global scope”.


A generic worker event-loop

At the end of the day, we get a generic event-loop logic, using WorkerEventLoopMethods for the parts that are worker-specific.

Through our use of associated items on the trait, we’re able to express the fact that T is a struct implementing a generic trait, in a way that the compiler appreciates…

https://github.com/servo/servo/commit/029715aba6f4a4c62b5a2759d47dda57954cc433#diff-38925b1ff4f20bfb4b257eb7a6e973d8R94