UNPKG

hamok

Version:

Lightweight Distributed Object Storage on RAFT consensus algorithm

143 lines (142 loc) 7.09 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createRaftCandidateState = createRaftCandidateState; const logger_1 = require("../common/logger"); const RaftVote_1 = require("../messages/messagetypes/RaftVote"); const RaftFollowerState_1 = require("./RaftFollowerState"); const RaftLeaderState_1 = require("./RaftLeaderState"); const logger = (0, logger_1.createLogger)('RaftCandidateState'); function createRaftCandidateState(context) { logger.trace('Creating RaftCandidateState'); const { raftEngine } = context; const config = raftEngine.config; const logs = raftEngine.logs; const messageEmitter = raftEngine.transport; const props = raftEngine.props; const localPeerId = raftEngine.localPeerId; const remotePeers = raftEngine.remotePeers; const respondedRemotePeerIds = new Set(); const receivedVotes = new Set([ localPeerId, // vote for itself ]); const notifiedRemotePeers = new Set(); let closed = false; let started; let elected = false; const appendEntriesRequestListener = (request) => { if (!remotePeers.has(request.peerId)) { logger.warn(`%s Received an append entries request from an unknown peer: ${request.peerId}`, localPeerId); } if (request.term < context.electionTerm) { return logger.debug('%s Received an append entries request from a peer with a smaller term than the current election term. Request: %o', localPeerId, request); } logger.info('%s Received an append entries request from a peer with a higher term than the current election term. Request: %o', localPeerId, request); // a leader has been elected, let's go back to the follower state return follow(); }; const appendRaftEntriesResponseListener = (response) => { if (!remotePeers.has(response.sourcePeerId)) { return logger.warn(`%s Received an append entries response from an unknown peer: ${response.sourcePeerId}`, localPeerId); } return logger.debug('%s Received an append entries response from %s', localPeerId, response.sourcePeerId); }; const voteRequestListener = (request) => { if (!remotePeers.has(request.candidateId)) { return logger.warn('%s Received a vote request from an unknown peer: %s. remotePeers: %s, request: %o', localPeerId, request.candidateId, Array.from(remotePeers).join(', '), request); } logger.debug('%s Received a vote request from %s', localPeerId, request.peerId); }; const voteResponseListener = (response) => { if (!remotePeers.has(response.sourcePeerId)) { return logger.warn(`Received a vote response from an unknown peer: ${response.sourcePeerId}`); } if (context.electionTerm < response.term) { // election should dismiss, case should be closed logger.warn(`%s Candidate received response from a higher term (${response.term}) than the current election term (${context.electionTerm}).`, localPeerId); return follow(); } if (response.term < context.electionTerm) { return logger.warn('A vote response from a term smaller than the current is received: %o', response); } respondedRemotePeerIds.add(response.sourcePeerId); if (!response.voteGranted) { return; } receivedVotes.add(response.sourcePeerId); const numberOfPeerIds = remotePeers.size + 1; // +1, because of a local peer! elected = numberOfPeerIds < receivedVotes.size * 2; logger.debug('%s Received vote for leadership: %d, number of peers: %d. Won: %d', localPeerId, receivedVotes.size, numberOfPeerIds, elected); if (elected) { // won the election return lead(); } }; const close = () => { if (closed) return; closed = true; messageEmitter.off('RaftVoteRequest', voteRequestListener); messageEmitter.off('RaftVoteResponse', voteResponseListener); messageEmitter.off('RaftAppendEntriesRequestChunk', appendEntriesRequestListener); messageEmitter.off('RaftAppendEntriesResponse', appendRaftEntriesResponseListener); logger.debug('%s Candidate state is closed', localPeerId); }; messageEmitter.on('RaftVoteRequest', voteRequestListener); messageEmitter.on('RaftVoteResponse', voteResponseListener); messageEmitter.on('RaftAppendEntriesRequestChunk', appendEntriesRequestListener); messageEmitter.on('RaftAppendEntriesResponse', appendRaftEntriesResponseListener); const run = () => { // prevent to send a vote request in case it is already elected if (elected) return; for (const remotePeerId of remotePeers) { if (notifiedRemotePeers.has(remotePeerId)) { continue; } const request = new RaftVote_1.RaftVoteRequest(context.electionTerm, logs.nextIndex - 1, props.currentTerm, remotePeerId, localPeerId); logger.debug('%s Send vote request to %s', localPeerId, request.peerId); messageEmitter.send(request); notifiedRemotePeers.add(remotePeerId); } if (!started) return (started = Date.now()); const elapsedTimeInMs = Date.now() - started; if (config.electionTimeoutInMs < elapsedTimeInMs) { // election timeout logger.warn(`${localPeerId} Timeout occurred during the election process (electionTimeoutInMs: ${config.electionTimeoutInMs}, elapsedTimeInMs: ${elapsedTimeInMs}, respondedRemotePeerIds: ${Array.from(respondedRemotePeerIds).join(', ')}). This can be a result because of split vote. elapsedTimeInMs: ${elapsedTimeInMs}`); return follow(); } }; const follow = () => { raftEngine.state = (0, RaftFollowerState_1.createRaftFollowerState)({ raftEngine, extraWaitingTime: Math.random() * 10000, }); }; const lead = () => { if (context.electionTerm <= props.currentTerm) { logger.warn('%s Candidate won the election, but the term has been changed. Current term: %d, Election term: %d', localPeerId, props.currentTerm, context.electionTerm); return follow(); } else if (props.currentTerm + 1 < context.electionTerm) { logger.warn('%s Candidate won the election, but the term has been changed. Current term: %d, Election term: %d', localPeerId, props.currentTerm, context.electionTerm); return follow(); } // won the election logger.debug('%s Won the election for term %d', localPeerId, context.electionTerm); props.currentTerm = context.electionTerm; raftEngine.state = (0, RaftLeaderState_1.createRaftLeaderState)({ raftEngine, }); }; return { stateName: 'candidate', run, close, }; }