UNPKG

swim

Version:

Gossip protocol based on SWIM

335 lines (267 loc) 10.3 kB
'use strict'; var debug = require('debug'); var events = require('events'); var farmhash = require('farmhash'); var util = require('util'); var FailureDetector = require('./failure-detector'); var Member = require('./member'); var MessageType = require('./message-type'); var Net = require('./net'); function Membership(opts) { this.swim = opts.swim; this.local = new Member(opts.local); this.suspectTimeout = opts.suspectTimeout || Membership.Default.suspectTimeout; this.preferCurrentMeta = opts.preferCurrentMeta || Membership.Default.preferCurrentMeta; this.ackListener = this.onAck.bind(this); this.updateListener = this.onUpdate.bind(this); this.suspectListener = this.onSuspect.bind(this); this.syncListener = this.onSync.bind(this); this.hostToMember = Object.create(null); this.hostToIterable = Object.create(null); this.hostToFaulty = Object.create(null); this.hostToSuspectTimeout = Object.create(null); this.debug = debug('swim:membership').bind(undefined, opts.debugIdentifier); } util.inherits(Membership, events.EventEmitter); Membership.prototype.start = function start() { var self = this; self.swim.failureDetector.on(FailureDetector.EventType.Suspect, self.suspectListener); self.swim.net.on(Net.EventType.Ack, self.ackListener); self.swim.net.on(Net.EventType.Sync, self.syncListener); self.swim.net.on(Net.EventType.Update, self.updateListener); Object.keys(self.hostToSuspectTimeout).forEach(function resumeSuspect(host) { self.onSuspect(self.get(host)); }); }; Membership.prototype.stop = function stop() { var self = this; self.swim.failureDetector.removeListener(FailureDetector.EventType.Suspect, self.suspectListener); self.swim.net.removeListener(Net.EventType.Ack, self.ackListener); self.swim.net.removeListener(Net.EventType.Sync, self.syncListener); self.swim.net.removeListener(Net.EventType.Update, self.updateListener); Object.keys(self.hostToSuspectTimeout).forEach(function clearTimeoutWithoutDeletion(host) { clearTimeout(self.hostToSuspectTimeout[host]); }); }; Membership.prototype.onAck = function onAck(data, host) { if (this.hostToMember[host] && this.hostToMember[host].state === Member.State.Suspect) { this.swim.net.sendMessage({ type: MessageType.Update, data: this.hostToMember[host].data() }, host); } }; Membership.prototype.onSuspect = function onSuspect(member) { var self = this; var data; member = new Member(member.data()); member.state = Member.State.Suspect; data = member.data(); clearTimeout(self.hostToSuspectTimeout[data.host]); delete self.hostToSuspectTimeout[data.host]; self.hostToSuspectTimeout[data.host] = setTimeout(function setFaulty() { delete self.hostToSuspectTimeout[data.host]; data.state = Member.State.Faulty; self.onUpdate(data); }, self.suspectTimeout); self.onUpdate(member.data()); }; Membership.prototype.onSync = function onSync(data) { var host = data.host; this.updateAlive(data); this.swim.net.sendMessages(this.all(true, true).map(function toMessage(data) { return { type: MessageType.Update, data: data }; }), host); }; Membership.prototype.sync = function sync(hosts) { var self = this; var messages = [{ type: MessageType.Sync, data: self.local.data() }]; this.all(false, true).forEach(function addMessage(data) { messages.push({ type: MessageType.Update, data: data }); }); hosts.forEach(function sendToHost(host) { self.swim.net.sendMessages(messages, host); }); }; Membership.prototype.onUpdate = function onUpdate(data) { this.debug('received update', data); switch (data.state) { case Member.State.Alive: this.updateAlive(data); break; case Member.State.Suspect: this.updateSuspect(data); break; case Member.State.Faulty: this.updateFaulty(data); break; } }; Membership.prototype.updateAlive = function updateAlive(data) { if (this.isLocal(data.host)) { if (this.local.incarnate(data, false, this.preferCurrentMeta)) { this.emit(Membership.EventType.Update, this.local.data()); } else { this.emit(Membership.EventType.Drop, data); } return; } if (this.hostToFaulty[data.host] && this.hostToFaulty[data.host].incarnation >= data.incarnation) { this.emit(Membership.EventType.Drop, data); return; } if (!this.hostToMember[data.host] || data.incarnation > this.hostToMember[data.host].incarnation) { clearTimeout(this.hostToSuspectTimeout[data.host]); delete this.hostToSuspectTimeout[data.host]; delete this.hostToFaulty[data.host]; if (!this.hostToMember[data.host]) { this.hostToMember[data.host] = new Member(data); this.hostToIterable[data.host] = this.hostToMember[data.host]; this.emit(Membership.EventType.Change, this.hostToMember[data.host].data()); } else { this.hostToMember[data.host] = new Member(data); } this.emit(Membership.EventType.Update, this.hostToMember[data.host].data()); } else { this.emit(Membership.EventType.Drop, data); } }; Membership.prototype.updateSuspect = function updateSuspect(data) { if (this.isLocal(data.host)) { this.emit(Membership.EventType.Drop, data); this.local.incarnate(data, true, this.preferCurrentMeta); this.emit(Membership.EventType.Update, this.local.data()); return; } if (this.hostToFaulty[data.host] && this.hostToFaulty[data.host].incarnation >= data.incarnation) { this.emit(Membership.EventType.Drop, data); return; } if (!this.hostToMember[data.host] || data.incarnation > this.hostToMember[data.host].incarnation || data.incarnation === this.hostToMember[data.host].incarnation && this.hostToMember[data.host].state === Member.State.Alive) { delete this.hostToFaulty[data.host]; if (!this.hostToMember[data.host]) { this.hostToMember[data.host] = new Member(data); this.hostToIterable[data.host] = this.hostToMember[data.host]; this.emit(Membership.EventType.Change, this.hostToMember[data.host].data()); } else { this.hostToMember[data.host] = new Member(data); } this.emit(Membership.EventType.Update, this.hostToMember[data.host].data()); } else { this.emit(Membership.EventType.Drop, data); } }; Membership.prototype.updateFaulty = function updateFaulty(data) { if (this.isLocal(data.host)) { this.emit(Membership.EventType.Drop, data); this.local.incarnate(data, true, this.preferCurrentMeta); this.emit(Membership.EventType.Update, this.local.data()); return; } if (this.hostToMember[data.host] && data.incarnation >= this.hostToMember[data.host].incarnation) { this.hostToFaulty[data.host] = new Member(data); delete this.hostToMember[data.host]; delete this.hostToIterable[data.host]; this.emit(Membership.EventType.Change, data); this.emit(Membership.EventType.Update, data); } else { this.emit(Membership.EventType.Drop, data); } }; Membership.prototype.next = function next() { var hosts = Object.keys(this.hostToIterable); var host; var member; if (hosts.length === 0) { this.shuffle(); hosts = Object.keys(this.hostToIterable); } host = hosts[Math.floor(Math.random() * hosts.length)]; member = this.hostToIterable[host]; delete this.hostToIterable[host]; return member; }; Membership.prototype.random = function random(n) { var hosts = Object.keys(this.hostToMember); var selected = []; var index; var i; for (i = 0; i < n && i < hosts.length; i++) { index = i + Math.floor(Math.random() * (hosts.length - i)); selected.push(this.hostToMember[hosts[index]]); hosts[index] = hosts[i]; } return selected; }; Membership.prototype.shuffle = function shuffle() { var self = this; self.hostToIterable = Object.create(null); Object.keys(self.hostToMember).forEach(function addToIterable(host) { self.hostToIterable[host] = self.hostToMember[host]; }); }; Membership.prototype.get = function get(host) { return this.hostToMember[host]; }; Membership.prototype.size = function size(hasLocal) { return Object.keys(this.hostToMember).length + (hasLocal ? 1 : 0); }; Membership.prototype.all = function all(hasLocal, hasFaulty) { var self = this; var results = Object.keys(self.hostToMember).map(function toData(host) { return self.hostToMember[host].data(); }); if (hasLocal) { results.push(self.local.data()); } if (hasFaulty) { Object.keys(self.hostToFaulty).forEach(function toData(host) { results.push(self.hostToFaulty[host].data()); }); } return results; }; Membership.prototype.checksum = function checksum() { var self = this; var strs = self.all(true).sort(function compare(a, b) { return parseInt(a.host.split(':')[1]) - parseInt(b.host.split(':')[1]); }).map(function toString(member) { return member.host + member.state + member.incarnation; }); return farmhash.hash64(strs.join('-')); }; Membership.prototype.isLocal = function isLocal(host) { return host === this.local.host; }; Membership.prototype.localhost = function localhost() { return this.local.host; }; Membership.prototype.updateMeta = function updateMeta(meta) { this.local.meta = meta; this.local.incarnate(); this.emit(Membership.EventType.Update, this.local.data()); }; Membership.Default = { suspectTimeout: 10, preferCurrentMeta: false }; Membership.EventType = { Change: 'change', Drop: 'drop', Update: 'update' }; module.exports = Membership;