Saturday, February 17, 2007

Serializing Signals without Atomic Compare and Swap

So basically, the problem with event handlers in IE6 is that they might become interrupted by other event handlers, making synchronization difficult. What you want is a more traditional event driven model where only one event handler can execute at a time. Essentially, you want a scheme that serializes signals so that the event handlers execute sequentially. Unfortunately, since JavaScript comes with no synchronization primitives like atomic compare and swap, so doing this is messy.

So I think I've come up with a scheme that lets you wrap event handlers in a function that will test if other event handlers are executing at the same time. If they are, it will add itself to a queue to be executed later. There's various annoying synchronization issues involved though, so this is not as simple as it sounds.

Here's the pseudocode for the event wrapper function:


globals root, queue
if root == null
root := true
execute own handler
root := null
while !queue.empty()
root := true
queue.dispatch()
root := null
else
queue.add(self)
And here's the pseudocode for the queue methods:


queue.add(fn)
globals end, isClaims[], handlers[], queueLength
myend := end
while(true)
if isClaimed[myend] != null
myend++
if myend >= queueLength
myend := 0
if myend == start
error!
continue
isClaimed[myend] := true
if handlers[myend] != null
continue
handlers[myend] := fn
break
end := myend

queue.empty()
globals isClaimed[], start
return isClaimed[start]==null

queue.dispatch()
globals isClaimed[], handlers[], start, end, queueLength
if isClaimed[start] != null
myend := end
if myend == start
myend++
if myend >= queueLength
myend := 0
end := myend
execute handlers[start]
handlers[start] := null
isClaimed[start] := null
start ++
if start >= queueLength
start := 0
And here's how you could code it up in JavaScript. Basically, I define a handler function. You can then use the handler function to wrap your own event handlers before setting them to listen for events:


<script>
// Initialization script that creates the "handler" function for wrapping
// handlers
(function () {
var SIZE = 100; // max number of simultaneous events
var root = false; // boolean, indicating whether
// event handler is the first one in a
// chain or not
var queue = new Array(SIZE); // Event handlers to be executed later
var thisQueue = new Array(SIZE); // Corresponding this objects
var isClaimed = new Array(SIZE); // reserves a spot in queue
var start = 0; // position in queue circular buffer
var end = 0;

// Define the global handler wrapper function
handler = function(eventHandler)
{
return function() {
if (root == false) {
root = true;
eventHandler.call(this);
root = false;
while (isClaimed[start] != null)
{
root = true;
if (isClaimed[start] != null) {
var myend = end;
if (myend == start) {
myend++;
if (myend >= SIZE) myend = 0;
end = myend;
}
queue[start].call(thisQueue[start]);
queue[start] = null;
thisQueue[start] = null;
isClaimed[start] = null;
start++;
if (start >= SIZE) start = 0;
}
root = false;
}
} else {
var myend = end;
while (true) {
if (isClaimed[myend] != null) {
myend++;
if (myend >= SIZE) myend = 0;
continue;
}
isClaimed[myend] = true;
if (queue[myend] != null) continue;
queue[myend] = eventHandler;
thisQueue[myend] = this;
break;
}
end = myend;
}
};
}
})(); // Execute the initialization code

function loaded()
{
alert('loaded ' + this.src);
}

var img1 = new Image();
var img2 = new Image();
img1.onload = handler(loaded);
img2.onload = handler(loaded);
img1.src = "test1.png";
img2.src = "test2.png";
alert('done')
</script>

No comments:

Post a Comment