landvex: Fixar och tester klara för alla komponenter
- Datafabrik: Dockerfile fix, agentorkestrering fungerar - Vision: Identify-modell, FAISS, OCR alla testade - API: Alla 7 integrationstester passerade - Upplösare: Entitetsupplösning verifierad
This commit is contained in:
+262
@@ -0,0 +1,262 @@
|
||||
import { sleep, randomToken } from './util.js';
|
||||
import unload from 'unload';
|
||||
|
||||
var LeaderElection = function LeaderElection(channel, options) {
|
||||
this._channel = channel;
|
||||
this._options = options;
|
||||
this.isLeader = false;
|
||||
this.isDead = false;
|
||||
this.token = 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 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 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);
|
||||
}
|
||||
|
||||
export function beLeader(leaderElector) {
|
||||
leaderElector.isLeader = true;
|
||||
var unloadFn = unload.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;
|
||||
}
|
||||
|
||||
export 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;
|
||||
}
|
||||
Reference in New Issue
Block a user