You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
---
slug: javascript-threadingpublished: 2007-11-08T00:00:00-08:00description: Not Everything Adheres to JavaScript's Semi-Single-Threaded Modelchangelog:
- on: 2020-02-23note: This was rescued from the archives of the old [felocity.org](https://web.archive.org/web/20050507121849/http://www.felocity.org/journal). As a result, many links in this piece were left pointing to archive.org destinations.
- on: 2020-02-24note: While I couldn't find the test page, I'm happy to report that this bug no longer exists in Firefox and IE on the Mac is also no longer a concern for JS engineers.
---
There's a lot of digging required to figure out if JavaScript is single or multi-threaded. Thanks to the work Dan Simard in June, a final answer on JavaScript threads was reached. The only major problem in this was that the comments by a user to Simard's post indicated that alert() caused Firefox's execution chain to change. The result was while the prompt was on screen, setTimeout() and other assorted callback functions were free to resolve.
The problem is made worse with several setTimeout() calls. As a single thread, JavaScript maintains some sort of event stack, where all of the callbacks and timeouts pile up, waiting for their turn. Once the event stack is out of events, the timers start again to count down to execution. If you fire one call with a setTimeout() and a timeout value of 2 seconds, and one more second of code execution follows, there will still be 2 seconds on the timer since it has not had a chance to run.
varfoo_count=0;varbar_count=0;varfoo=function(){++foo_count;window.setTimeout(bar,5);};varbar=function(){++bar_count;alert("foo="+foo_count+", bar="+bar_count);};// triggerswindow.setTimeout(foo,10);window.setTimeout(bar,20);// end triggers
In a perfectly timed environment (running just this code), foo will execute, followed by bar, followed by foo's call to bar. In reality, the millisecond calibration for this is iffy. The output comes out as foo=1, bar=2 then foo=1, bar=1. However, simply looking at the timing values, our alert calls should be reversed. We've entered the world of volatile variables and shared space without even knowing (and probably not even wanting). Under the hood, it appears that making a call to alert(), prompt(), confirm(), or any other window level call pauses the current thread's execution, letting any pending setTimeout() calls resolve. When the execution returns from the window event, variables that were scoped one block higher could have changed without the knowledge of the current execution block.
A more practical example of this can be found on the thread safe JS Example Page. As of this writing, IE and Opera execute things properly in order, waiting for the prompt() to finish before continuing the current code block, and after completion, letting the pending setTimeout() event fire. In Firefox and Safari, during the prompt() call, the setTimeout() executes, overwriting the value of bar. [Ed-- unfortunately, the test page is currently MIA. If you find it, I'd love to relink it!]
Protecting a Volatile Bar
As mentioned before, when the browser's JS Interpreter hands off to the alert(), it begins resolving any waiting setTimeout() calls that in in the event queue. In our example, another call to bar() is waiting which also increments bar_count. By the time we alert bar_count, something has gone and changed it. This seems to also hold true for prompt() and confirm() in Firefox and a few other browsers.
Imagine instead of just our primitive window.setTimeout() we were using an AJAX callback which waited for a DOM node to be available. The costs of this shared variable space being violated is a bit more costly. In many other languages, there are Mutexes for the synchronized calls, and the volatile declarative to avoid problems. Because the implementation of ECMAScript is unique to each browser, there needs to be a consistent way to secure data from tampering during the application's critical moments.
At our disposal...
new Object() is atomic (can't be interrupted by browser events)
return (new Object() === new Object()); is always false as each new object is given a unique ID by the system. We need a way to atomically create an ID for a fake thread, and the Object's deep equality can do just that given it is atomic.
Array.push() is atomic (can't be interrupted by browser events) as long as we are using the native implementation. Even if two objects push onto an array in a race condition, only one will hold the coveted index of 0.
(function() { new Object(); })(); is garbage collected properly
block scope level variables cannot be changed by outside factors
That doesn't leave us with much. Ming made a post on JS Mutexes though that gave me an idea. The biggest concern from Ming's entries was that in Internet Explorer, events such as onload can interrupt a process, creating multiple events at the same time. To get around this, we would need a method which the browser cannot interrupt. This is where the native Array.push() comes in handy. For some reason, IE treats this call as non-interruptible, letting us have a queue process that ensures element 0 is the first lock requester. If we have two atomic commands, one to uniquely identify a lock request and another to enqueue the lock, then we have everything we need for a very basic spinlock implementation. Even if a second event interrupts our two statements, the pushing onto the array guarantees only one of the two requests will be given rights to the lock. We'll know which function got the lock thanks to our deep quality === of objects.
Spinlock design implies a "busy wait" cycle for getting the lock, meaning each individual thread usually sits in the equivalent of while(1) {}. Because we don't have threads and instead have the event queue, a while loop would seize the system. To get around this, we can use the same setTimeout() call that was the source of our problem to "sleep" an event until the lock resource is available. The result is a very lazy locking method that requires the application to care about the lock (but a lock that can be used in many different ways). Our pseudo code looks like:
sub process
lock = get lock
if not lock owner
retry sub process in N ms (sleep)
return
lock was obtained
perform critical code
release lock
non critical segment here
end process
Return to the thread safe test page and try the protected version. Across all browsers, the lock is held until the our asynchronous events properly completed.
Beyond Locking Down Variables
A locking system like this can be extended to deal with more than just the JavaScript Variables. In the case of lazy loading JavaScript, it is possible to be using the same utility to load several "sets" of JavaScript at the same time. To avoid keeping the document.head open (and risking Operation Aborted problems), we use the same locking strategy to keep a DOM node from getting written to until Internet Explorer reports it as closed, such as an onreadystate for a script tags.
The locking utility can also be used to create "run once" functions such as an init() for a class outside of its constructor. The class can simply request the lock, and on completion, refuse to release it. While this takes up a small bit of memory in the lock utility, it also guarantees only one init() reaches the critical section for the life of the page.
Limitations
Building such a locking system in JavaScript is not without drawbacks. The most notable of these is the lack of support for browsers which do not support Array.push() (hello, Internet Explorer on a Mac). If you have this esoteric corner case, I'm stumped; there's not much you can do.
It's also unknown just how atomic the methods are in non-browsers. While brief tests in Rhino imply that Array.push() is properly atomic, there's no guarantee that it is in any specification.
📚 CodePosts with questionable syntax decisions🏷 JavaScriptIt's been a hell of a ride
1 participant
Converted from issue
This discussion was converted from issue #9 on December 25, 2021 08:01.
Heading
Bold
Italic
Quote
Code
Link
Numbered list
Unordered list
Task list
Attach files
Mention
Reference
Menu
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
There's a lot of digging required to figure out if JavaScript is single or multi-threaded. Thanks to the work Dan Simard in June, a final answer on JavaScript threads was reached. The only major problem in this was that the comments by a user to Simard's post indicated that
alert()
caused Firefox's execution chain to change. The result was while the prompt was on screen,setTimeout()
and other assorted callback functions were free to resolve.The problem is made worse with several
setTimeout()
calls. As a single thread, JavaScript maintains some sort of event stack, where all of the callbacks and timeouts pile up, waiting for their turn. Once the event stack is out of events, the timers start again to count down to execution. If you fire one call with asetTimeout()
and a timeout value of 2 seconds, and one more second of code execution follows, there will still be 2 seconds on the timer since it has not had a chance to run.In a perfectly timed environment (running just this code),
foo
will execute, followed bybar
, followed byfoo
's call tobar
. In reality, the millisecond calibration for this is iffy. The output comes out asfoo=1
,bar=2
thenfoo=1
,bar=1
. However, simply looking at the timing values, our alert calls should be reversed. We've entered the world of volatile variables and shared space without even knowing (and probably not even wanting). Under the hood, it appears that making a call toalert()
,prompt()
,confirm()
, or any other window level call pauses the current thread's execution, letting any pendingsetTimeout()
calls resolve. When the execution returns from the window event, variables that were scoped one block higher could have changed without the knowledge of the current execution block.A more practical example of this can be found on the thread safe
JS Example Page. As of this writing, IE and Opera execute things properly in order, waiting for the prompt() to finish before continuing the current code block, and after completion, letting the pendingsetTimeout()
event fire. In Firefox and Safari, during theprompt()
call, thesetTimeout()
executes, overwriting the value of bar. [Ed-- unfortunately, the test page is currently MIA. If you find it, I'd love to relink it!]Protecting a Volatile Bar
As mentioned before, when the browser's JS Interpreter hands off to the
alert()
, it begins resolving any waitingsetTimeout()
calls that in in the event queue. In our example, another call tobar()
is waiting which also incrementsbar_count
. By the time we alertbar_count
, something has gone and changed it. This seems to also hold true forprompt()
andconfirm()
in Firefox and a few other browsers.Imagine instead of just our primitive
window.setTimeout()
we were using an AJAX callback which waited for a DOM node to be available. The costs of this shared variable space being violated is a bit more costly. In many other languages, there are Mutexes for the synchronized calls, and the volatile declarative to avoid problems. Because the implementation of ECMAScript is unique to each browser, there needs to be a consistent way to secure data from tampering during the application's critical moments.At our disposal...
new Object()
is atomic (can't be interrupted by browser events)return (new Object() === new Object());
is always false as each new object is given a unique ID by the system. We need a way to atomically create an ID for a fake thread, and the Object's deep equality can do just that given it is atomic.Array.push()
is atomic (can't be interrupted by browser events) as long as we are using the native implementation. Even if two objects push onto an array in a race condition, only one will hold the coveted index of0
.(function() { new Object(); })();
is garbage collected properlyThat doesn't leave us with much. Ming made a post on JS Mutexes though that gave me an idea. The biggest concern from Ming's entries was that in Internet Explorer, events such as onload can interrupt a process, creating multiple events at the same time. To get around this, we would need a method which the browser cannot interrupt. This is where the native
Array.push()
comes in handy. For some reason, IE treats this call as non-interruptible, letting us have a queue process that ensures element0
is the first lock requester. If we have two atomic commands, one to uniquely identify a lock request and another to enqueue the lock, then we have everything we need for a very basic spinlock implementation. Even if a second event interrupts our two statements, the pushing onto the array guarantees only one of the two requests will be given rights to the lock. We'll know which function got the lock thanks to our deep quality===
of objects.Spinlock design implies a "busy wait" cycle for getting the lock, meaning each individual thread usually sits in the equivalent of
while(1) {}
. Because we don't have threads and instead have the event queue, a while loop would seize the system. To get around this, we can use the samesetTimeout()
call that was the source of our problem to "sleep" an event until the lock resource is available. The result is a very lazy locking method that requires the application to care about the lock (but a lock that can be used in many different ways). Our pseudo code looks like:Return to the
thread safe test pageand try the protected version. Across all browsers, the lock is held until the our asynchronous events properly completed.Beyond Locking Down Variables
A locking system like this can be extended to deal with more than just the JavaScript Variables. In the case of lazy loading JavaScript, it is possible to be using the same utility to load several "sets" of JavaScript at the same time. To avoid keeping the
document.head
open (and risking Operation Aborted problems), we use the same locking strategy to keep a DOM node from getting written to until Internet Explorer reports it as closed, such as anonreadystate
for a script tags.The locking utility can also be used to create "run once" functions such as an
init()
for a class outside of its constructor. The class can simply request the lock, and on completion, refuse to release it. While this takes up a small bit of memory in the lock utility, it also guarantees only oneinit()
reaches the critical section for the life of the page.Limitations
Building such a locking system in JavaScript is not without drawbacks. The most notable of these is the lack of support for browsers which do not support
Array.push()
(hello, Internet Explorer on a Mac). If you have this esoteric corner case, I'm stumped; there's not much you can do.It's also unknown just how atomic the methods are in non-browsers. While brief tests in Rhino imply that
Array.push()
is properly atomic, there's no guarantee that it is in any specification.Beta Was this translation helpful? Give feedback.
All reactions