nodeway-raft
Version:
It is an implementation of the Raft consensus algorithm in Nodeway.
116 lines (109 loc) • 3.79 kB
JavaScript
const Nodeway = require('nodeway/lib/Event');
const serverId= require('random-uuid-v4')();
const require2= require('require-from-url/async');
const State = require('./state.js');
var uuid2id= {};
var id2uuid= {};
var peers = {};
function majority() {
return Math.ceil(Object.keys(peers).length/2);
}
function emit(id, eventName, arg) {
let api = Nodeway.onlineUsers[id2uuid[id]];
if (api && api.link2) {
let data = typeof arg==='function' ? arg(id) : arg;
if (eventName==='AppendEntries' && typeof data.success==='undefined') {
if (api.link2.raft) {
if (api.link2.raft.data.term===data.term &&
api.link2.raft.data.prevLogIndex===data.prevLogIndex &&
api.link2.raft.data.prevLogTerm===data.prevLogTerm &&
api.link2.raft.data.entries.length===data.entries.length &&
api.link2.raft.data.commitIndex===data.commitIndex) return;
api.link2.raft.count++;
}
else {
api.link2.raft = {count: 1};
}
api.link2.raft.data = data;
}
api.emit(eventName, data);
}
}
function broadcast(eventName, arg) {
for(let uuid in peers) emit(uuid2id[uuid], eventName, arg);
}
var state = new State({majority, emit, broadcast});
class Raft extends Nodeway {
constructor(uuid) {
super(uuid);
}
async info() {
return {
id: serverId,
polls: Object.keys(peers).map(uuid => ({
uuid,
url : peers[uuid].url,
link: uuid2id[uuid]
})),
pushs: Object.keys(Nodeway.onlineUsers).map(uuid => ({
uuid,
link: Object.keys(id2uuid).find(id => id2uuid[id]===uuid)
})),
state: state.get()
}
}
async related(id) {
id2uuid[id] = this.uuid;
return serverId;
}
async join(url) {
let [API] = await require2([url]),
api = new API;
api.on('RequestVote', arg => state.onRequestVote(uuid2id[API._UUID_], arg));
api.on('AppendEntries', arg => {
let id = uuid2id[API._UUID_]
if (typeof arg.success!=='undefined') {
let api = Nodeway.onlineUsers[id2uuid[id]]
api &&
api.link2 &&
api.link2.raft &&
api.link2.raft.count--===1 &&
delete api.link2.raft;
}
state.onAppendEntries(id, arg);
});
api.on('ClientRequest', arg => state.onClientRequest(uuid2id[API._UUID_], arg));
peers[API._UUID_] = {url, api}
uuid2id[API._UUID_] = await api.related(serverId);
}
async leave(id) {
let uuid = Object.keys(uuid2id).find(uuid => uuid2id[uuid]===id);
if(!uuid) throw new Error('not found');
let api = peers[uuid].api;
api.on('RequestVote');
api.on('AppendEntries');
api.on('ClientRequest');
delete peers[uuid];
delete uuid2id[uuid];
delete id2uuid[id];
}
async follower() {
if(!state.is('weakened')) throw new Error('!weakened');
for(let uuid in peers)
if(!id2uuid[uuid2id[uuid]]) throw new Error(peers[uuid].url);
state.set('follower');
}
async weakened() {
if(!state.is('follower')) throw new Error('!follower');
state.set('weakened');
}
async client(command) {
return new Promise(function(resolve, reject) {
state.clientRequest({serverId, command}, function(err, response) {
!err ? resolve(response) : reject(err);
});
});
}
}
module.exports = Raft;
;