UNPKG

nodeway-raft

Version:

It is an implementation of the Raft consensus algorithm in Nodeway.

116 lines (109 loc) 3.79 kB
'use strict'; 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;