swim
Version:
Gossip protocol based on SWIM
235 lines (197 loc) • 6.57 kB
JavaScript
'use strict';
var debug = require('debug');
var events = require('events');
var util = require('util');
var Codec = require('./codec');
var Disseminator = require('./disseminator');
var FailureDetector = require('./failure-detector');
var Membership = require('./membership');
var Net = require('./net');
var JoinFailedError = require('./error').JoinFailedError;
var InvalidStateError = require('./error').InvalidStateError;
var ListenFailedError = require('./error').ListenFailedError;
function Swim(opts) {
this.opts = opts;
this.codec = new Codec({
codec: opts.codec,
swim: this,
debugIdentifier: opts.local.host
});
this.disseminator = new Disseminator({
disseminationFactor: opts.disseminationFactor,
swim: this,
debugIdentifier: opts.local.host
});
this.failureDetector = new FailureDetector({
interval: opts.interval,
pingTimeout: opts.pingTimeout,
pingReqTimeout: opts.pingReqTimeout,
pingReqGroupSize: opts.pingReqGroupSize,
swim: this,
debugIdentifier: opts.local.host
});
this.membership = new Membership({
local: opts.local,
suspectTimeout: opts.suspectTimeout,
preferCurrentMeta: opts.preferCurrentMeta,
swim: this,
debugIdentifier: opts.local.host
});
this.net = new Net({
udp: {
port: parseInt(opts.local.host.split(':')[1]),
type: opts.udp && opts.udp.type,
maxDgramSize: opts.udp && opts.udp.maxDgramSize
},
swim: this,
debugIdentifier: opts.local.host
});
this.state = Swim.State.Stopped;
this.joinTimeout = opts.joinTimeout || Swim.Default.joinTimeout;
this.joinCheckInterval = opts.joinCheckInterval || Swim.Default.joinCheckInterval;
this.changeListener = this.emit.bind(this, Swim.EventType.Change);
this.updateListener = this.emit.bind(this, Swim.EventType.Update);
this.debug = debug('swim').bind(undefined, opts.local.host);
}
util.inherits(Swim, events.EventEmitter);
Swim.prototype.bootstrap = function bootstrap(hosts, callback) {
var self = this;
var err;
if (self.state !== Swim.State.Stopped) {
err = new InvalidStateError({
current: self.state,
expected: Swim.State.Stopped
});
if (typeof callback === 'function') {
callback(err);
} else {
self.emit(Swim.EventType.Error, err);
}
return;
}
self.net.listen(function onListen(err) {
if (err) {
err = new ListenFailedError({
host: self.opts.local.host,
type: self.opts.udp.type
});
if (typeof callback === 'function') {
callback(err);
} else {
self.emit(Swim.EventType.Error, err);
}
return;
}
self.failureDetector.start();
self.membership.start();
self.disseminator.start();
self.membership.on(Membership.EventType.Change, self.changeListener);
self.membership.on(Membership.EventType.Update, self.updateListener);
self.state = Swim.State.Started;
self.join(hosts, callback);
});
};
Swim.prototype.join = function join(hosts, callback) {
var self = this;
var err;
if (self.state !== Swim.State.Started) {
err = new InvalidStateError({
currect: self.state,
expected: Swim.State.Started
});
if (typeof callback === 'function') {
callback(err);
} else {
self.emit(Swim.EventType.Error, err);
}
}
if (!hosts || hosts.length === 0) {
if (typeof callback === 'function') {
callback();
} else {
self.emit(Swim.EventType.Ready);
}
return;
}
hosts = hosts.filter(function notLocal(host) {
return host !== self.opts.local.host;
});
self.membership.sync(hosts);
var checker = null;
var timeout = setTimeout(function joinTimeout() {
clearInterval(checker);
var numberOfHostsResponded = checkJoin();
if (numberOfHostsResponded === 0) {
var err = new JoinFailedError({
local: self.localhost(),
hosts: hosts,
numberOfHostsResponded: numberOfHostsResponded,
timeout: self.joinTimeout
});
if (typeof callback === 'function') {
callback(err);
} else {
self.emit(Swim.EventType.Error, err);
}
}
}, self.joinTimeout);
if (self.joinCheckInterval < self.joinTimeout) {
checker = setInterval(checkJoin, self.joinCheckInterval);
}
function checkJoin() {
var numberOfHostsResponded = hosts.reduce(function countRespondedHosts(num, host) {
num += self.membership.get(host) ? 1 : 0;
return num;
}, 0);
if (numberOfHostsResponded >= 1) {
clearInterval(checker);
clearTimeout(timeout);
if (typeof callback === 'function') {
callback();
} else {
self.emit(Swim.EventType.Ready);
}
}
return numberOfHostsResponded;
}
};
Swim.prototype.leave = function leave() {
this.membership.removeListener(Membership.EventType.Update, this.updateListener);
this.membership.removeListener(Membership.EventType.Change, this.changeListener);
this.disseminator.stop();
this.membership.stop();
this.failureDetector.stop();
this.net.close();
this.state = Swim.State.Stopped;
};
Swim.prototype.members = function members(hasLocal, hasFaulty) {
return this.membership.all(hasLocal, hasFaulty);
};
Swim.prototype.checksum = function checksum() {
return this.membership.checksum();
};
Swim.prototype.localhost = function localhost() {
return this.membership && this.membership.localhost() || this.opts.local.host;
};
Swim.prototype.whoami = function whoami() {
return this.localhost();
};
Swim.prototype.updateMeta = function updateMeta(meta) {
return this.membership.updateMeta(meta);
};
Swim.Default = {
joinTimeout: 300,
joinCheckInterval: 50
};
Swim.EventType = {
Change: 'change',
Update: 'update',
Error: 'error',
Ready: 'ready'
};
Swim.State = {
Started: 'started',
Stopped: 'stopped'
};
module.exports = Swim;
module.exports.Error = require('./error');