0

Let's say I had a WebWorker file called ticker.js that consisted of this:

function tick() {
  postMessage(1);
}

setInterval(tick, 1);

And then in the main JavaScript program I had this:

let ready = true;
let prevTime = 0;

function sometimesLongTask() {
  ready = false;

  if (performance.now() - prevTime > 30) {
    // a very long CPU intensive task, like a loop which takes a while to complete

    prevTime = performance.now();
  }

  ready = true;
}

const intervalWorker = new Worker('ticker.js');

intervalWorker.onmessage = function() {
  ready && sometimesLongTask();
};

Would this cause a memory leak?

I think it won't, but I'm not sure. My logic is that while sometimesLongTask will sometimes take a while (when 30 milliseconds have passed), 99% of the time it will execute immediately. As such, even though a new tick is added to Javascript's event queue every millisecond, every once in a while Javascript can speed through all of those and clear them out since the majority of them will not need to execute. Is this indeed the case?

Also, do I even need the ready flag here, or is it doing nothing (and I can get rid of it)? I think it might be doing nothing because Javascript is single-threaded, so even though the WebWorker might post multiple messages very quickly, Javascript cannot run multiple instances of sometimesLongTask at the same time. Correct?

Ryan Peschel
  • 11,087
  • 19
  • 74
  • 136

1 Answers1

1

Yes, this won't cause a memory leak, since that by definitions means memory allocations not being released despite no longer being needed. The events in the message queue are still needed though, they will eventually be received by the worker.

But you won't even get growing memory usage. This is solely due to the prevTime comparison though - we can assume that all queued events can be handled (by doing nothing) in the 30ms between the runs of the long task.

Do I even need the ready flag here, or is it doing nothing?

It indeed does nothing useful. Notice it is always true when the onmessage handler is executed. The onmessage handler will never interrupt your synchronous sometimesLongTask during which it is set to false.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Perfect, thanks for the explanation, especially the last part. – Ryan Peschel Apr 24 '22 at 20:00
  • "we can assume that all queued events can be handled (by doing nothing) in the 30ms between the runs of the long task", this is true mostly because they use `setInterval`, which will have a minimum 4ms delay at the 5th iteration. Using a faster interval (e.g postTask, or a MessageChannel) could actually be problematic, even with the 30ms breath time. See [this pen](https://codepen.io/_-0-_/pen/GRybjBy) for instance, 3s of such a fast interval ends up in 2000ms of event handling in my Chrome. This means that about 1970ms of work will get added to the queue at every iteration. – Kaiido Apr 26 '22 at 02:56
  • @Kaiido Yes, limiting the `postMessage` calls themselves would be a better option if you have a really fast process emitting messages. I started to explore this option in my first attempt at an answer (see below) but then abandoned it as I had not completely understood what the OP's `if` statement was doing – Bergi Apr 26 '22 at 03:22
  • I believe OP is trying to circumvent the throttling that browsers impose on background tabs, and thus they opted to use a Worker as a timer. Limiting the postMessage from the Worker in such circumstances sounds impractical. They are actually already debouncing the onmessage handler as you were proposing in your deleted answer, but my point is that even debounced that may lead to an ever-growing queue, because even "doing nothing" takes actual time. So if a future reader comes here with a much faster interval they may face the issue OP was afraid of. – Kaiido Apr 26 '22 at 03:31
  • @Kaiido I have no idea what the OP is doing, I was wondering why they are posting messages to run a task from the worker to the tab and not the other way round. And yes, debouncing `onmessage` doesn't cut it (I wrote that before I understood it is what the OP already is doing), the second suggestion would have been to debounce `postMessage` instead – Bergi Apr 26 '22 at 03:51
  • So send the `ready` to the Worker instead of keeping it local where it's useless. – Kaiido Apr 26 '22 at 03:52
  • @Kaiido Exactly – Bergi Apr 26 '22 at 03:53
  • Sounds like it would be a pretty good addition to this answer then ;-) Just `worker.postMessage("I'm blocked"); blockUI(); worker.postMessage("I'm free");` would do. – Kaiido Apr 26 '22 at 03:56
  • @Kaiido Just to clarify what I'm doing: this allows my game's physics to run normally even when the tab is not focused. If I use `setInterval` instead on the main thread (without using a WebWorker), when the tab is not focused the intervals only run once every second. It's just a bit of a hack (which apparently works on all browsers and has been for 10 years now) which allows the game's physics to always run, regardless of whether or not the tab is focused. If I don't do this then there's a huge amount of lag when tabbing back into the tab (processing all the missed physics) – Ryan Peschel Apr 27 '22 at 14:22
  • @Bergi Poking you as well because I don't think you can poke multiple people in the same comment – Ryan Peschel Apr 27 '22 at 14:23
  • Ah, interesting. In that case [my comment on your other question](https://stackoverflow.com/questions/71984730/how-to-prevent-javascript-from-suspending-when-the-user-tabs-out#comment127198631_71984730) still applies. You might want to treat tabbing out of the browser just the same as closing and re-opening the tab. – Bergi Apr 27 '22 at 17:41