Multi-Threaded JavaScript – Not What You’d Wish For Exactly

SquirrelFish.jpg

I ran into a bug in my web app today that I should have seen coming, but my head hadn't been in the multi-threaded space at the time, so I missed it. The core concept is that JavaScript can be driven on multiple threads (timer, user interaction, callback, etc.) and if there are multiple threads doing things to the same codebase, you have a multi-threaded program, and you'd like all the tools a general multi-threaded language.

In my reading, I have heard that certain JavaScript engine implementations are single-threaded, but that's not part of the specification, and it's up to the implementors to decide whether or not to make the engine multi-core. In general, I think it's more likely that as time goes on, more engines will be multi-threaded rather than less.

The code I was having problems with this morning looked like this:

  prevRange = graph.getVisibleChartRange();
  if ((prevRange.start.getTime() == maxGraphRange.start.getTime()) &&
      (prevRange.end.getTime() == maxGraphRange.end.getTime()))) {
    prevRange = null;
  }

where I'm getting the selected range on the Google Visualization AnnotatedTimeLine widget and comparing that to the original (un-zoomed) values, and if they are the same, I'm nulling it out. The reason for this is to preserve the zoom level across changes to the graph, but the problem is very tricky.

Since JavaScript is inherently multi-threaded, I have to be aware of the possibility that things are being changed underneath you from timers and AJAX requests... so there's the possibility that while you're running this code, the graph itself is updated with data and redrawn, and in that redrawing, the data is invalid, and there will be no valid range on the graph. Resulting in the first line returning null.

This null then creates a null pointer exception in the code when it's asked for start in the second line. Not nice. So I had to protect the code from that possibility by saying:

  prevRange = graph.getVisibleChartRange();
  if ((prevRange != null) &&
      (prevRange.start.getTime() == maxGraphRange.start.getTime()) &&
      (prevRange.end.getTime() == maxGraphRange.end.getTime()))) {
    prevRange = null;
  }

The problem with this is that it's possible that there was a zoomed range, and in the redraw, and just plain bad timing, we're going to loose that because the method getVisibleChartRange() is going to return null when it was just incapable of returning anything meaningful. The better solution would be to have a mutex on the graph, and have it not return anything while it's redrawing, and then when it's done, return the answer.

But that would presume the concept of a JavaScript mutex. But nothing like it exists. Odd, isn't it? It just hit me this morning, and I did a bit of Googling to see if there were anything I was missing in JavaScript or a library that might act as a mutex, but there really isn't. That's too bad. Really too bad.

What it means is that there's going to be something like it one day, and that's going to make coding in JavaScript much nicer, and it also means that there's nothing I can really do to fix up the code. There is a slim possibility that there will be an update to the graph while hitting that one section of code that will be invalidated by the inability to lock (block) the update.

I suppose I could think about setting a value before the update and then restoring it afterwards... Hmmm... something to think about.