We’re creating a word processor that runs in the browser. A web worker recalculating the layout of the document in the background seemed at first to be a no-brainer. That would have no impact on the UI and could recalculate as a background thread. This is definitely how you would code it if it was a desktop application.
So, I coded the whole thing up, with async messages between the main/UI thread and the layout worker. It all worked great.
And… we then threw that away and now do the layout in the main thread.
Severe Constraints of Web Workers
Why did we abandon web workers?
First, you cannot share memory between the two threads. You need to pass JSON objects (it’s a bit more than just JSON but close enough for this discussion) from one thread to the other. So you’ve now added in passing the parts of the document object you need from the layout thread to the main thread. In the overhead of doing this you need to have a way to specify what you want, copy that out, and pass it across.
One nice thing about how it passes an object across is it includes everything it points at. And those objects include everything they point at. And those objects… So you don’t have any pointers to unresolved objects.
One really bad thing about how it passes an object across is it includes everything it points at. And those objects include everything they point at. And those objects… So we could end up copying the entire document object across. That would kill performance & responsiveness. Your choice at this point is to either design the document objects so they are descrete page objects that do not point at each other, or build an object to copy across on each request. Both approaches have big downsides.
Second, the worker thread is not interruptible. When you post a message to it, that message is not processed until the message you are currently processing has completed. If we’re calculating the layout and there is another change such as the user typed another letter, we need to stop our work and re-start. The only way to do this is to calculate layout for 1/60th of a second, then call setTimer() to continue the layout, then return. We may then get a message telling us to re-start. So the hope of avoiding using setTimer() in the main thread – we just moved that approach, we didn’t eliminate it.
Ok all of the above is a giant pain but programming is not always easy. Sometimes you have to do complex things for the optimal solution. But we then hit a third item that killed this.
Third, the web workers added unnecessary complexity. Our layout code is fast. Really, really fast. We can lay out a 200-page document in under 2 seconds. Our code was so fast, there was no need to have it performed in background processing. Most layout is within a paragraph. Next most is from the edit location to the end of the page. Aside from global style changes, layout never has to go past a hard page break. So we can perform the layout in the main thread. We can do it without even using the setTimeout() trick because it’s so fast. And then all code uses the same document object. And if the layout code does not return until the layout is completely recalculated, then it is safe for any other code to operate on the document object as it is always consistent.
Web Workers: One Instance of Beautiful Code but a Sub-Optimal Approach
If you’re considering a web worker, keep the above in mind. It’s a different world from a worker thread in a Java or C# application. Same concept, but very severe constraints. I probably spent over a week across the different parts of this implementing the web worker communication, time slicing, etc. It was really beautiful code and did an elegant job solving the problem. But it was a sub-optimal approach and so we threw it all away.
Got something to say about web workers? Think there’s something I missed? Go ahead and let me know in the comments below.
Author: David Thielen
Dave, Windward's founder and CEO, is passionate about building superb software teams from scratch and dramatically improving the productivity of existing software teams. He's really proud that he once created a game so compelling (Enemy Nations) that a now-professional World of Warcraft player lost his job for playing it incessantly on company time. You can read more from Dave on his personal blog, and at Huffington Post.
Other posts by David Thielen