274 lines
6.9 KiB
JavaScript
274 lines
6.9 KiB
JavaScript
"use strict";
|
|
|
|
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.beLeader = beLeader;
|
|
exports.createLeaderElection = createLeaderElection;
|
|
|
|
var _util = require("./util.js");
|
|
|
|
var _unload = _interopRequireDefault(require("unload"));
|
|
|
|
var LeaderElection = function LeaderElection(channel, options) {
|
|
this._channel = channel;
|
|
this._options = options;
|
|
this.isLeader = false;
|
|
this.isDead = false;
|
|
this.token = (0, _util.randomToken)();
|
|
this._isApl = false; // _isApplying
|
|
|
|
this._reApply = false; // things to clean up
|
|
|
|
this._unl = []; // _unloads
|
|
|
|
this._lstns = []; // _listeners
|
|
|
|
this._invs = []; // _intervals
|
|
|
|
this._dpL = function () {}; // onduplicate listener
|
|
|
|
|
|
this._dpLC = false; // true when onduplicate called
|
|
};
|
|
|
|
LeaderElection.prototype = {
|
|
applyOnce: function applyOnce() {
|
|
var _this = this;
|
|
|
|
if (this.isLeader) return Promise.resolve(false);
|
|
if (this.isDead) return Promise.resolve(false); // do nothing if already running
|
|
|
|
if (this._isApl) {
|
|
this._reApply = true;
|
|
return Promise.resolve(false);
|
|
}
|
|
|
|
this._isApl = true;
|
|
var stopCriteria = false;
|
|
var recieved = [];
|
|
|
|
var handleMessage = function handleMessage(msg) {
|
|
if (msg.context === 'leader' && msg.token != _this.token) {
|
|
recieved.push(msg);
|
|
|
|
if (msg.action === 'apply') {
|
|
// other is applying
|
|
if (msg.token > _this.token) {
|
|
// other has higher token, stop applying
|
|
stopCriteria = true;
|
|
}
|
|
}
|
|
|
|
if (msg.action === 'tell') {
|
|
// other is already leader
|
|
stopCriteria = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
this._channel.addEventListener('internal', handleMessage);
|
|
|
|
var ret = _sendMessage(this, 'apply') // send out that this one is applying
|
|
.then(function () {
|
|
return (0, _util.sleep)(_this._options.responseTime);
|
|
}) // let others time to respond
|
|
.then(function () {
|
|
if (stopCriteria) return Promise.reject(new Error());else return _sendMessage(_this, 'apply');
|
|
}).then(function () {
|
|
return (0, _util.sleep)(_this._options.responseTime);
|
|
}) // let others time to respond
|
|
.then(function () {
|
|
if (stopCriteria) return Promise.reject(new Error());else return _sendMessage(_this);
|
|
}).then(function () {
|
|
return beLeader(_this);
|
|
}) // no one disagreed -> this one is now leader
|
|
.then(function () {
|
|
return true;
|
|
})["catch"](function () {
|
|
return false;
|
|
}) // apply not successfull
|
|
.then(function (success) {
|
|
_this._channel.removeEventListener('internal', handleMessage);
|
|
|
|
_this._isApl = false;
|
|
|
|
if (!success && _this._reApply) {
|
|
_this._reApply = false;
|
|
return _this.applyOnce();
|
|
} else return success;
|
|
});
|
|
|
|
return ret;
|
|
},
|
|
awaitLeadership: function awaitLeadership() {
|
|
if (
|
|
/* _awaitLeadershipPromise */
|
|
!this._aLP) {
|
|
this._aLP = _awaitLeadershipOnce(this);
|
|
}
|
|
|
|
return this._aLP;
|
|
},
|
|
|
|
set onduplicate(fn) {
|
|
this._dpL = fn;
|
|
},
|
|
|
|
die: function die() {
|
|
var _this2 = this;
|
|
|
|
if (this.isDead) return;
|
|
this.isDead = true;
|
|
|
|
this._lstns.forEach(function (listener) {
|
|
return _this2._channel.removeEventListener('internal', listener);
|
|
});
|
|
|
|
this._invs.forEach(function (interval) {
|
|
return clearInterval(interval);
|
|
});
|
|
|
|
this._unl.forEach(function (uFn) {
|
|
uFn.remove();
|
|
});
|
|
|
|
return _sendMessage(this, 'death');
|
|
}
|
|
};
|
|
|
|
function _awaitLeadershipOnce(leaderElector) {
|
|
if (leaderElector.isLeader) return Promise.resolve();
|
|
return new Promise(function (res) {
|
|
var resolved = false;
|
|
|
|
function finish() {
|
|
if (resolved) {
|
|
return;
|
|
}
|
|
|
|
resolved = true;
|
|
clearInterval(interval);
|
|
|
|
leaderElector._channel.removeEventListener('internal', whenDeathListener);
|
|
|
|
res(true);
|
|
} // try once now
|
|
|
|
|
|
leaderElector.applyOnce().then(function () {
|
|
if (leaderElector.isLeader) {
|
|
finish();
|
|
}
|
|
}); // try on fallbackInterval
|
|
|
|
var interval = setInterval(function () {
|
|
leaderElector.applyOnce().then(function () {
|
|
if (leaderElector.isLeader) {
|
|
finish();
|
|
}
|
|
});
|
|
}, leaderElector._options.fallbackInterval);
|
|
|
|
leaderElector._invs.push(interval); // try when other leader dies
|
|
|
|
|
|
var whenDeathListener = function whenDeathListener(msg) {
|
|
if (msg.context === 'leader' && msg.action === 'death') {
|
|
leaderElector.applyOnce().then(function () {
|
|
if (leaderElector.isLeader) finish();
|
|
});
|
|
}
|
|
};
|
|
|
|
leaderElector._channel.addEventListener('internal', whenDeathListener);
|
|
|
|
leaderElector._lstns.push(whenDeathListener);
|
|
});
|
|
}
|
|
/**
|
|
* sends and internal message over the broadcast-channel
|
|
*/
|
|
|
|
|
|
function _sendMessage(leaderElector, action) {
|
|
var msgJson = {
|
|
context: 'leader',
|
|
action: action,
|
|
token: leaderElector.token
|
|
};
|
|
return leaderElector._channel.postInternal(msgJson);
|
|
}
|
|
|
|
function beLeader(leaderElector) {
|
|
leaderElector.isLeader = true;
|
|
|
|
var unloadFn = _unload["default"].add(function () {
|
|
return leaderElector.die();
|
|
});
|
|
|
|
leaderElector._unl.push(unloadFn);
|
|
|
|
var isLeaderListener = function isLeaderListener(msg) {
|
|
if (msg.context === 'leader' && msg.action === 'apply') {
|
|
_sendMessage(leaderElector, 'tell');
|
|
}
|
|
|
|
if (msg.context === 'leader' && msg.action === 'tell' && !leaderElector._dpLC) {
|
|
/**
|
|
* another instance is also leader!
|
|
* This can happen on rare events
|
|
* like when the CPU is at 100% for long time
|
|
* or the tabs are open very long and the browser throttles them.
|
|
* @link https://github.com/pubkey/broadcast-channel/issues/414
|
|
* @link https://github.com/pubkey/broadcast-channel/issues/385
|
|
*/
|
|
leaderElector._dpLC = true;
|
|
|
|
leaderElector._dpL(); // message the lib user so the app can handle the problem
|
|
|
|
|
|
_sendMessage(leaderElector, 'tell'); // ensure other leader also knows the problem
|
|
|
|
}
|
|
};
|
|
|
|
leaderElector._channel.addEventListener('internal', isLeaderListener);
|
|
|
|
leaderElector._lstns.push(isLeaderListener);
|
|
|
|
return _sendMessage(leaderElector, 'tell');
|
|
}
|
|
|
|
function fillOptionsWithDefaults(options, channel) {
|
|
if (!options) options = {};
|
|
options = JSON.parse(JSON.stringify(options));
|
|
|
|
if (!options.fallbackInterval) {
|
|
options.fallbackInterval = 3000;
|
|
}
|
|
|
|
if (!options.responseTime) {
|
|
options.responseTime = channel.method.averageResponseTime(channel.options);
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
function createLeaderElection(channel, options) {
|
|
if (channel._leaderElector) {
|
|
throw new Error('BroadcastChannel already has a leader-elector');
|
|
}
|
|
|
|
options = fillOptionsWithDefaults(options, channel);
|
|
var elector = new LeaderElection(channel, options);
|
|
|
|
channel._befC.push(function () {
|
|
return elector.die();
|
|
});
|
|
|
|
channel._leaderElector = elector;
|
|
return elector;
|
|
} |