Changeset View
Changeset View
Standalone View
Standalone View
webroot/rsrc/externals/javelin/lib/Leader.js
Show All 21 Lines | |||||
* used in conjunction with @{method:callIfLeader} to allow multiple event | * used in conjunction with @{method:callIfLeader} to allow multiple event | ||||
* responders to trigger a reaction to an event (like a sound) and ensure that | * responders to trigger a reaction to an event (like a sound) and ensure that | ||||
* it is played only once (not once for each notification), and by only one | * it is played only once (not once for each notification), and by only one | ||||
* tab (not once for each tab). | * tab (not once for each tab). | ||||
* | * | ||||
* Finally, tabs can register a callback which will run if they become the | * Finally, tabs can register a callback which will run if they become the | ||||
* leading tab, by listening for `onBecomeLeader`. | * leading tab, by listening for `onBecomeLeader`. | ||||
*/ | */ | ||||
JX.install('Leader', { | JX.install('Leader', { | ||||
events: ['onBecomeLeader', 'onReceiveBroadcast'], | events: ['onBecomeLeader', 'onReceiveBroadcast'], | ||||
statics: { | statics: { | ||||
_interval: null, | _interval: null, | ||||
_broadcastKey: 'JX.Leader.broadcast', | _broadcastKey: 'JX.Leader.broadcast', | ||||
_leaderKey: 'JX.Leader.id', | _leaderKey: 'JX.Leader.id', | ||||
Show All 30 Lines | statics: { | ||||
/** | /** | ||||
* Call a method if this tab is the leader. | * Call a method if this tab is the leader. | ||||
* | * | ||||
* This is asynchronous because leadership election is asynchronous. If | * This is asynchronous because leadership election is asynchronous. If | ||||
* the current tab is not the leader after any election takes place, the | * the current tab is not the leader after any election takes place, the | ||||
* callback will not be invoked. | * callback will not be invoked. | ||||
*/ | */ | ||||
callIfLeader: function(callback) { | callIfLeader: function(callback) { | ||||
JX.Leader._callIf(callback, JX.bag); | |||||
}, | |||||
/** | |||||
* Call a method after leader election. | |||||
* | |||||
* This is asynchronous because leadership election is asynchronous. The | |||||
* callback will be invoked after election takes place. | |||||
* | |||||
* This method is useful if you want to invoke a callback no matter what, | |||||
* but the callback behavior depends on whether this is the leader or | |||||
* not. | |||||
*/ | |||||
call: function(callback) { | |||||
JX.Leader._callIf(callback, callback); | |||||
}, | |||||
epriestley: I needed an "always call after election" method. | |||||
/** | |||||
* Elect a leader, then invoke either a leader callback or a follower | |||||
* callback. | |||||
*/ | |||||
_callIf: function(leader_callback, follower_callback) { | |||||
if (!window.localStorage) { | if (!window.localStorage) { | ||||
// If we don't have localStorage, pretend we're the only tab. | // If we don't have localStorage, pretend we're the only tab. | ||||
self._becomeLeader(); | self._becomeLeader(); | ||||
callback(); | leader_callback(); | ||||
return; | return; | ||||
} | } | ||||
var self = JX.Leader; | var self = JX.Leader; | ||||
// If we don't have an ID for this tab yet, generate one and register | // If we don't have an ID for this tab yet, generate one and register | ||||
// event listeners. | // event listeners. | ||||
if (!self._id) { | if (!self._id) { | ||||
Show All 13 Lines | _callIf: function(leader_callback, follower_callback) { | ||||
// If we haven't installed an update timer yet, do so now. This will | // If we haven't installed an update timer yet, do so now. This will | ||||
// renew our lease every 5 seconds, making sure we hold it until the | // renew our lease every 5 seconds, making sure we hold it until the | ||||
// tab is closed. | // tab is closed. | ||||
if (!self._interval && lease.until > now + 10000) { | if (!self._interval && lease.until > now + 10000) { | ||||
self._interval = window.setInterval(self._write, 5000); | self._interval = window.setInterval(self._write, 5000); | ||||
} | } | ||||
self._becomeLeader(); | self._becomeLeader(); | ||||
callback(); | leader_callback(); | ||||
} else { | |||||
follower_callback(); | |||||
} | } | ||||
return; | return; | ||||
} | } | ||||
// If the lease isn't good, try to become the leader. We don't have | // If the lease isn't good, try to become the leader. We don't have | ||||
// proper locking primitives for this, but can do a relatively good | // proper locking primitives for this, but can do a relatively good | ||||
// job. The algorithm here is: | // job. The algorithm here is: | ||||
// | // | ||||
Show All 9 Lines | _callIf: function(leader_callback, follower_callback) { | ||||
// This approximately follows an algorithm attributed to Fischer in | // This approximately follows an algorithm attributed to Fischer in | ||||
// "A Fast Mutual Exclusion Algorithm" (Leslie Lamport, 1985). That | // "A Fast Mutual Exclusion Algorithm" (Leslie Lamport, 1985). That | ||||
// paper also describes a faster (but more complex) algorithm, but | // paper also describes a faster (but more complex) algorithm, but | ||||
// it's not problematic to add a significant delay here because | // it's not problematic to add a significant delay here because | ||||
// leader election is not especially performance-sensitive. | // leader election is not especially performance-sensitive. | ||||
self._write(); | self._write(); | ||||
window.setTimeout(JX.bind(null, self.callIfLeader, callback), 50); | window.setTimeout( | ||||
JX.bind(null, self._callIf, leader_callback, follower_callback), | |||||
50); | |||||
}, | }, | ||||
/** | /** | ||||
* Send a message to all open tabs. | * Send a message to all open tabs. | ||||
* | * | ||||
* Tabs can receive messages by listening to `onReceiveBroadcast`. | * Tabs can receive messages by listening to `onReceiveBroadcast`. | ||||
* | * | ||||
▲ Show 20 Lines • Show All 136 Lines • Show Last 20 Lines |
I needed an "always call after election" method.