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.