All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.aeron.cluster.Election Maven / Gradle / Ivy

There is a newer version: 1.46.2
Show newest version
/*
 * Copyright 2014-2019 Real Logic Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.aeron.cluster;

import io.aeron.*;
import io.aeron.cluster.client.ClusterException;
import io.aeron.cluster.service.Cluster;
import org.agrona.CloseHelper;
import org.agrona.collections.Int2ObjectHashMap;

import java.util.Random;
import java.util.concurrent.TimeUnit;

import static io.aeron.Aeron.NULL_VALUE;
import static io.aeron.archive.client.AeronArchive.NULL_POSITION;
import static io.aeron.cluster.ClusterMember.compareLog;

/**
 * Election process to determine a new cluster leader.
 */
class Election implements AutoCloseable
{
    /**
     * The multiplier applied to the {@link ConsensusModule.Configuration#ELECTION_STATUS_INTERVAL_PROP_NAME}
     * for the nomination timeout.
     */
    static final int NOMINATION_TIMEOUT_MULTIPLIER = 7;

    /**
     * The type id of the {@link Counter} used for the election state.
     */
    static final int ELECTION_STATE_TYPE_ID = 207;

    enum State
    {
        INIT(0),
        CANVASS(1),

        NOMINATE(2),
        CANDIDATE_BALLOT(3),
        FOLLOWER_BALLOT(4),

        LEADER_REPLAY(5),
        LEADER_TRANSITION(6),
        LEADER_READY(7),

        FOLLOWER_REPLAY(8),
        FOLLOWER_CATCHUP_TRANSITION(9),
        FOLLOWER_CATCHUP(10),
        FOLLOWER_TRANSITION(11),
        FOLLOWER_READY(12);

        static final State[] STATES;

        static
        {
            final State[] states = values();
            STATES = new State[states.length];
            for (final State state : states)
            {
                final int code = state.code();
                if (null != STATES[code])
                {
                    throw new ClusterException("code already in use: " + code);
                }

                STATES[code] = state;
            }
        }

        private final int code;

        State(final int code)
        {
            this.code = code;
        }

        int code()
        {
            return code;
        }

        static State get(final int code)
        {
            if (code < 0 || code > (STATES.length - 1))
            {
                throw new ClusterException("invalid state counter code: " + code);
            }

            return STATES[code];
        }
    }

    private boolean isStartup;
    private boolean shouldReplay;
    private final long electionStatusIntervalMs;
    private final long electionTimeoutMs;
    private final long leaderHeartbeatIntervalMs;
    private final long leaderHeartbeatTimeoutMs;
    private final ClusterMember[] clusterMembers;
    private final ClusterMember thisMember;
    private final Int2ObjectHashMap clusterMemberByIdMap;
    private final MemberStatusAdapter memberStatusAdapter;
    private final MemberStatusPublisher memberStatusPublisher;
    private final ConsensusModule.Context ctx;
    private final ConsensusModuleAgent consensusModuleAgent;
    private final Random random;

    private long timeOfLastStateChangeMs;
    private long timeOfLastUpdateMs;
    private long nominationDeadlineMs;
    private long logPosition;
    private long catchupLogPosition = NULL_POSITION;
    private long leadershipTermId;
    private long logLeadershipTermId;
    private long candidateTermId = NULL_VALUE;
    private int logSessionId = CommonContext.NULL_SESSION_ID;
    private ClusterMember leaderMember = null;
    private State state = State.INIT;
    private Counter stateCounter;
    private Subscription logSubscription;
    private String liveLogDestination;
    private LogReplay logReplay = null;

    Election(
        final boolean isStartup,
        final long leadershipTermId,
        final long logPosition,
        final ClusterMember[] clusterMembers,
        final Int2ObjectHashMap clusterMemberByIdMap,
        final ClusterMember thisMember,
        final MemberStatusAdapter memberStatusAdapter,
        final MemberStatusPublisher memberStatusPublisher,
        final ConsensusModule.Context ctx,
        final ConsensusModuleAgent consensusModuleAgent)
    {
        this.isStartup = isStartup;
        this.shouldReplay = isStartup;
        this.electionStatusIntervalMs = TimeUnit.NANOSECONDS.toMillis(ctx.electionStatusIntervalNs());
        this.electionTimeoutMs = TimeUnit.NANOSECONDS.toMillis(ctx.electionTimeoutNs());
        this.leaderHeartbeatIntervalMs = TimeUnit.NANOSECONDS.toMillis(ctx.leaderHeartbeatIntervalNs());
        this.leaderHeartbeatTimeoutMs = TimeUnit.NANOSECONDS.toMillis(ctx.leaderHeartbeatTimeoutNs());
        this.logPosition = logPosition;
        this.logLeadershipTermId = leadershipTermId;
        this.leadershipTermId = leadershipTermId;
        this.clusterMembers = clusterMembers;
        this.clusterMemberByIdMap = clusterMemberByIdMap;
        this.thisMember = thisMember;
        this.memberStatusAdapter = memberStatusAdapter;
        this.memberStatusPublisher = memberStatusPublisher;
        this.ctx = ctx;
        this.consensusModuleAgent = consensusModuleAgent;
        this.random = ctx.random();
    }

    public void close()
    {
        CloseHelper.close(stateCounter);
    }

    int doWork(final long nowMs)
    {
        int workCount = State.INIT == state ? init(nowMs) : 0;
        workCount += memberStatusAdapter.poll();

        switch (state)
        {
            case CANVASS:
                workCount += canvass(nowMs);
                break;

            case NOMINATE:
                workCount += nominate(nowMs);
                break;

            case CANDIDATE_BALLOT:
                workCount += candidateBallot(nowMs);
                break;

            case FOLLOWER_BALLOT:
                workCount += followerBallot(nowMs);
                break;

            case LEADER_REPLAY:
                workCount += leaderReplay(nowMs);
                break;

            case LEADER_TRANSITION:
                workCount += leaderTransition(nowMs);
                break;

            case LEADER_READY:
                workCount += leaderReady(nowMs);
                break;

            case FOLLOWER_REPLAY:
                workCount += followerReplay(nowMs);
                break;

            case FOLLOWER_CATCHUP_TRANSITION:
                workCount += followerCatchupTransition(nowMs);
                break;

            case FOLLOWER_CATCHUP:
                workCount += followerCatchup(nowMs);
                break;

            case FOLLOWER_TRANSITION:
                workCount += followerTransition(nowMs);
                break;

            case FOLLOWER_READY:
                workCount += followerReady(nowMs);
                break;
        }

        return workCount;
    }

    void onCanvassPosition(final long logLeadershipTermId, final long logPosition, final int followerMemberId)
    {
        final ClusterMember follower = clusterMemberByIdMap.get(followerMemberId);

        if (null == follower)
        {
            return;
        }

        follower
            .leadershipTermId(logLeadershipTermId)
            .logPosition(logPosition);

        if (State.LEADER_READY == state && logLeadershipTermId < leadershipTermId)
        {
            if (this.logLeadershipTermId == logLeadershipTermId)
            {
                publishNewLeadershipTerm(follower.publication(), leadershipTermId);
            }
            else
            {
                memberStatusPublisher.newLeadershipTerm(
                    follower.publication(),
                    logLeadershipTermId,
                    consensusModuleAgent.logStopPosition(logLeadershipTermId),
                    logLeadershipTermId + 1,
                    this.logPosition,
                    thisMember.id(),
                    logSessionId);
            }
        }
        else if (State.CANVASS != state && logLeadershipTermId > leadershipTermId)
        {
            state(State.CANVASS, ctx.epochClock().time());
        }
    }

    void onRequestVote(
        final long logLeadershipTermId, final long logPosition, final long candidateTermId, final int candidateId)
    {
        if (candidateTermId <= leadershipTermId || candidateTermId <= this.candidateTermId)
        {
            placeVote(candidateTermId, candidateId, false);
        }
        else if (compareLog(this.logLeadershipTermId, this.logPosition, logLeadershipTermId, logPosition) > 0)
        {
            this.candidateTermId = candidateTermId;
            ctx.clusterMarkFile().candidateTermId(candidateTermId);
            state(State.CANVASS, ctx.epochClock().time());

            placeVote(candidateTermId, candidateId, false);
        }
        else
        {
            this.candidateTermId = candidateTermId;
            ctx.clusterMarkFile().candidateTermId(candidateTermId);
            state(State.FOLLOWER_BALLOT, ctx.epochClock().time());

            placeVote(candidateTermId, candidateId, true);
        }
    }

    void onVote(
        final long candidateTermId,
        final long logLeadershipTermId,
        final long logPosition,
        final int candidateMemberId,
        final int followerMemberId,
        final boolean vote)
    {
        final ClusterMember follower = clusterMemberByIdMap.get(followerMemberId);

        if (State.CANDIDATE_BALLOT == state &&
            candidateTermId == this.candidateTermId &&
            candidateMemberId == thisMember.id() &&
            null != follower)
        {
            follower
                .candidateTermId(candidateTermId)
                .leadershipTermId(logLeadershipTermId)
                .logPosition(logPosition)
                .vote(vote ? Boolean.TRUE : Boolean.FALSE);
        }
    }

    @SuppressWarnings("unused")
    void onNewLeadershipTerm(
        final long logLeadershipTermId,
        final long logPosition,
        final long leadershipTermId,
        final long maxLogPosition,
        final int leaderMemberId,
        final int logSessionId)
    {
        final ClusterMember leader = clusterMemberByIdMap.get(leaderMemberId);

        if (null == leader)
        {
            return;
        }

        if ((State.FOLLOWER_BALLOT == state || State.CANDIDATE_BALLOT == state || State.CANVASS == state) &&
            leadershipTermId == this.candidateTermId)
        {
            this.leadershipTermId = leadershipTermId;
            leaderMember = leader;
            this.logSessionId = logSessionId;

            if (this.logPosition < logPosition && NULL_POSITION == catchupLogPosition)
            {
                catchupLogPosition = logPosition;

                state(State.FOLLOWER_REPLAY, ctx.epochClock().time());
            }
            else if (this.logPosition > logPosition && this.logLeadershipTermId == logLeadershipTermId)
            {
                consensusModuleAgent.truncateLogEntry(logLeadershipTermId, logPosition);
                this.logPosition = logPosition;
                state(State.FOLLOWER_REPLAY, ctx.epochClock().time());
            }
            else
            {
                catchupLogPosition = logPosition;
                state(State.FOLLOWER_REPLAY, ctx.epochClock().time());
            }
        }
        else if (0 != compareLog(this.logLeadershipTermId, this.logPosition, logLeadershipTermId, logPosition))
        {
            if (this.logPosition > logPosition && this.logLeadershipTermId == logLeadershipTermId)
            {
                consensusModuleAgent.truncateLogEntry(logLeadershipTermId, logPosition);
                this.logPosition = logPosition;
                state(State.FOLLOWER_REPLAY, ctx.epochClock().time());
            }
            else if (this.logPosition < logPosition && NULL_POSITION == catchupLogPosition)
            {
                this.leadershipTermId = leadershipTermId;
                this.candidateTermId = NULL_VALUE;
                leaderMember = leader;
                this.logSessionId = logSessionId;
                catchupLogPosition = logPosition;

                state(State.FOLLOWER_REPLAY, ctx.epochClock().time());
            }
            else if (logPosition == this.logPosition && NULL_POSITION == catchupLogPosition)
            {
                // may have left at this exact point from previous log
                this.leadershipTermId = this.leadershipTermId + 1;
                this.candidateTermId = this.candidateTermId + 1;
            }
        }
    }

    void onAppendedPosition(final long leadershipTermId, final long logPosition, final int followerMemberId)
    {
        final ClusterMember follower = clusterMemberByIdMap.get(followerMemberId);

        if (null != follower)
        {
            follower
                .logPosition(logPosition)
                .leadershipTermId(leadershipTermId);
            consensusModuleAgent.checkCatchupStop(follower);
        }
    }

    @SuppressWarnings("unused")
    void onCommitPosition(final long leadershipTermId, final long logPosition, final int leaderMemberId)
    {
        if (State.FOLLOWER_BALLOT == state && leadershipTermId > this.leadershipTermId)
        {
            if (this.logPosition > logPosition)
            {
                consensusModuleAgent.truncateLogEntry(logLeadershipTermId, logPosition);
            }
            else
            {
                catchupLogPosition = logPosition;
                state(State.FOLLOWER_REPLAY, ctx.epochClock().time());
            }
        }
        else if (State.FOLLOWER_CATCHUP == state && NULL_POSITION != catchupLogPosition)
        {
            catchupLogPosition = Math.max(catchupLogPosition, logPosition);
        }
    }

    void onReplayNewLeadershipTermEvent(
        final long logRecordingId, final long leadershipTermId, final long logPosition, final long nowMs)
    {
        if (State.FOLLOWER_CATCHUP == state)
        {
            this.logLeadershipTermId = leadershipTermId;
            this.logPosition = logPosition;

            ctx.recordingLog().appendTerm(logRecordingId, leadershipTermId, logPosition, nowMs);
            ctx.recordingLog().force();
        }
    }

    State state()
    {
        return state;
    }

    boolean notReplaying()
    {
        return State.FOLLOWER_READY != state && State.LEADER_REPLAY != state;
    }

    ClusterMember leader()
    {
        return leaderMember;
    }

    long leadershipTermId()
    {
        return leadershipTermId;
    }

    long candidateTermId()
    {
        return candidateTermId;
    }

    void logSessionId(final int logSessionId)
    {
        this.logSessionId = logSessionId;
    }

    long logPosition()
    {
        return logPosition;
    }

    private int init(final long nowMs)
    {
        stateCounter = ctx.aeron().addCounter(ELECTION_STATE_TYPE_ID, "Election State");

        if (!isStartup)
        {
            logPosition = consensusModuleAgent.prepareForElection(logPosition);
        }

        candidateTermId = Math.max(ctx.clusterMarkFile().candidateTermId(), leadershipTermId);

        if (clusterMembers.length == 1)
        {
            candidateTermId = Math.max(leadershipTermId + 1, candidateTermId + 1);
            leaderMember = thisMember;
            state(State.LEADER_REPLAY, nowMs);
        }
        else
        {
            state(State.CANVASS, nowMs);
        }

        return 1;
    }

    private int canvass(final long nowMs)
    {
        int workCount = 0;

        if (nowMs >= (timeOfLastUpdateMs + electionStatusIntervalMs))
        {
            timeOfLastUpdateMs = nowMs;
            for (final ClusterMember member : clusterMembers)
            {
                if (member != thisMember)
                {
                    memberStatusPublisher.canvassPosition(
                        member.publication(), leadershipTermId, logPosition, thisMember.id());
                }
            }

            workCount += 1;
        }

        if (ctx.appointedLeaderId() != NULL_VALUE && ctx.appointedLeaderId() != thisMember.id())
        {
            return workCount;
        }

        final long canvassDeadlineMs = (isStartup ?
            TimeUnit.NANOSECONDS.toMillis(ctx.startupCanvassTimeoutNs()) : electionTimeoutMs) +
            timeOfLastStateChangeMs;

        if (ClusterMember.isUnanimousCandidate(clusterMembers, thisMember) ||
            (ClusterMember.isQuorumCandidate(clusterMembers, thisMember) && nowMs >= canvassDeadlineMs))
        {
            nominationDeadlineMs =
                nowMs + random.nextInt((int)electionStatusIntervalMs * NOMINATION_TIMEOUT_MULTIPLIER);
            state(State.NOMINATE, nowMs);
            workCount += 1;
        }

        return workCount;
    }

    private int nominate(final long nowMs)
    {
        if (nowMs >= nominationDeadlineMs)
        {
            candidateTermId = Math.max(leadershipTermId + 1, candidateTermId + 1);
            ClusterMember.becomeCandidate(clusterMembers, candidateTermId, thisMember.id());
            ctx.clusterMarkFile().candidateTermId(candidateTermId);
            state(State.CANDIDATE_BALLOT, nowMs);
            return 1;
        }

        return 0;
    }

    private int candidateBallot(final long nowMs)
    {
        int workCount = 0;

        if (ClusterMember.hasWonVoteOnFullCount(clusterMembers, candidateTermId) ||
            ClusterMember.hasMajorityVoteWithCanvassMembers(clusterMembers, candidateTermId))
        {
            leaderMember = thisMember;
            state(State.LEADER_REPLAY, nowMs);
            workCount += 1;
        }
        else if (nowMs >= (timeOfLastStateChangeMs + electionTimeoutMs))
        {
            if (ClusterMember.hasMajorityVote(clusterMembers, candidateTermId))
            {
                leaderMember = thisMember;
                state(State.LEADER_REPLAY, nowMs);
            }
            else
            {
                state(State.CANVASS, nowMs);
            }

            workCount += 1;
        }
        else
        {
            for (final ClusterMember member : clusterMembers)
            {
                if (!member.isBallotSent())
                {
                    workCount += 1;
                    member.isBallotSent(memberStatusPublisher.requestVote(
                        member.publication(), leadershipTermId, logPosition, candidateTermId, thisMember.id()));
                }
            }
        }

        return workCount;
    }

    private int followerBallot(final long nowMs)
    {
        int workCount = 0;

        if (nowMs >= (timeOfLastStateChangeMs + electionTimeoutMs))
        {
            state(State.CANVASS, nowMs);
            workCount += 1;
        }

        return workCount;
    }

    private int leaderReplay(final long nowMs)
    {
        int workCount = 0;

        if (null == logReplay)
        {
            consensusModuleAgent.followerCommitPosition(logPosition);
            logSessionId = consensusModuleAgent.addNewLogPublication().sessionId();

            ClusterMember.resetLogPositions(clusterMembers, NULL_POSITION);
            thisMember.logPosition(logPosition).leadershipTermId(candidateTermId);

            if (!shouldReplay || (logReplay = consensusModuleAgent.newLogReplay(logPosition)) == null)
            {
                shouldReplay = false;
                state(State.LEADER_TRANSITION, nowMs);
                workCount = 1;
            }
        }
        else
        {
            workCount += logReplay.doWork(nowMs);
            if (logReplay.isDone())
            {
                logReplay.close();
                logReplay = null;
                shouldReplay = false;
                state(State.LEADER_TRANSITION, nowMs);
            }
            else if (nowMs > (timeOfLastUpdateMs + leaderHeartbeatIntervalMs))
            {
                timeOfLastUpdateMs = nowMs;

                for (final ClusterMember member : clusterMembers)
                {
                    if (member != thisMember)
                    {
                        publishNewLeadershipTerm(member.publication(), candidateTermId);
                    }
                }

                workCount += 1;
            }
        }

        return workCount;
    }

    private int leaderTransition(final long nowMs)
    {
        consensusModuleAgent.becomeLeader(candidateTermId, logPosition, logSessionId);

        for (long termId = leadershipTermId + 1; termId < candidateTermId; termId++)
        {
            ctx.recordingLog().appendTerm(NULL_VALUE, termId, logPosition, nowMs);
        }

        leadershipTermId = candidateTermId;

        ctx.recordingLog().appendTerm(consensusModuleAgent.logRecordingId(), leadershipTermId, logPosition, nowMs);
        ctx.recordingLog().force();

        state(State.LEADER_READY, nowMs);

        return 1;
    }

    private int leaderReady(final long nowMs)
    {
        int workCount = 0;

        if (ClusterMember.haveVotersReachedPosition(clusterMembers, logPosition, leadershipTermId))
        {
            if (consensusModuleAgent.electionComplete(nowMs))
            {
                consensusModuleAgent.updateMemberDetails(this);
                close();
            }

            workCount += 1;
        }
        else if (nowMs > (timeOfLastUpdateMs + leaderHeartbeatIntervalMs))
        {
            timeOfLastUpdateMs = nowMs;

            for (final ClusterMember member : clusterMembers)
            {
                if (member != thisMember)
                {
                    publishNewLeadershipTerm(member.publication(), leadershipTermId);
                }
            }

            workCount += 1;
        }

        return workCount;
    }

    private int followerReplay(final long nowMs)
    {
        int workCount = 0;

        final State nextState = NULL_POSITION != catchupLogPosition ?
            State.FOLLOWER_CATCHUP_TRANSITION : State.FOLLOWER_TRANSITION;

        if (null == logReplay)
        {
            if (!shouldReplay || (logReplay = consensusModuleAgent.newLogReplay(logPosition)) == null)
            {
                shouldReplay = false;
                state(nextState, nowMs);
                workCount = 1;
            }
        }
        else
        {
            workCount += logReplay.doWork(nowMs);
            if (logReplay.isDone())
            {
                logReplay.close();
                logReplay = null;
                shouldReplay = false;
                state(nextState, nowMs);
            }
        }

        return workCount;
    }

    private int followerCatchupTransition(final long nowMs)
    {
        if (null == logSubscription)
        {
            final ChannelUri logChannelUri = followerLogChannel(ctx.logChannel(), logSessionId);

            logSubscription = consensusModuleAgent.createAndRecordLogSubscriptionAsFollower(logChannelUri.toString());
            consensusModuleAgent.awaitServicesReady(logChannelUri, logSessionId, logPosition);

            final String replayDestination = new ChannelUriStringBuilder()
                .media(CommonContext.UDP_MEDIA)
                .endpoint(thisMember.transferEndpoint())
                .build();

            logSubscription.addDestination(replayDestination);
            consensusModuleAgent.replayLogDestination(replayDestination);
        }

        if (catchupPosition(leadershipTermId, logPosition))
        {
            state(State.FOLLOWER_CATCHUP, nowMs);
        }

        return 1;
    }

    private int followerCatchup(final long nowMs)
    {
        int workCount = 0;

        consensusModuleAgent.catchupLogPoll(logSubscription, logSessionId, catchupLogPosition);

        if (null == liveLogDestination &&
            consensusModuleAgent.hasAppendReachedLivePosition(logSubscription, logSessionId, catchupLogPosition))
        {
            addLiveLogDestination();
        }

        if (consensusModuleAgent.hasAppendReachedPosition(logSubscription, logSessionId, catchupLogPosition))
        {
            logPosition = catchupLogPosition;
            state(State.FOLLOWER_TRANSITION, nowMs);
            workCount += 1;
        }

        return workCount;
    }

    private int followerTransition(final long nowMs)
    {
        if (null == logSubscription)
        {
            final ChannelUri logChannelUri = followerLogChannel(ctx.logChannel(), logSessionId);

            logSubscription = consensusModuleAgent.createAndRecordLogSubscriptionAsFollower(logChannelUri.toString());
            consensusModuleAgent.awaitServicesReady(logChannelUri, logSessionId, logPosition);
        }

        if (null == liveLogDestination)
        {
            addLiveLogDestination();
        }

        consensusModuleAgent.awaitImageAndCreateFollowerLogAdapter(logSubscription, logSessionId);
        if (!ctx.recordingLog().hasTermBeenAppended(leadershipTermId))
        {
            ctx.recordingLog().appendTerm(consensusModuleAgent.logRecordingId(), leadershipTermId, logPosition, nowMs);
            ctx.recordingLog().force();
        }

        state(State.FOLLOWER_READY, nowMs);

        return 1;
    }

    private int followerReady(final long nowMs)
    {
        final Publication publication = leaderMember.publication();

        if (memberStatusPublisher.appendedPosition(publication, leadershipTermId, logPosition, thisMember.id()))
        {
            if (consensusModuleAgent.electionComplete(nowMs))
            {
                consensusModuleAgent.updateMemberDetails(this);
                close();
            }
        }
        else if (nowMs >= (timeOfLastStateChangeMs + leaderHeartbeatTimeoutMs))
        {
            if (null != liveLogDestination)
            {
                logSubscription.removeDestination(liveLogDestination);
                liveLogDestination = null;
                consensusModuleAgent.liveLogDestination(null);
            }

            state(State.CANVASS, nowMs);
        }

        return 1;
    }

    private void placeVote(final long candidateTermId, final int candidateId, final boolean vote)
    {
        final ClusterMember candidate = clusterMemberByIdMap.get(candidateId);

        if (null != candidate)
        {
            memberStatusPublisher.placeVote(
                candidate.publication(),
                candidateTermId,
                logLeadershipTermId,
                logPosition,
                candidateId,
                thisMember.id(),
                vote);
        }
    }

    private void publishNewLeadershipTerm(final Publication publication, final long leadershipTermId)
    {
        memberStatusPublisher.newLeadershipTerm(
            publication,
            logLeadershipTermId,
            logPosition,
            leadershipTermId,
            logPosition,
            thisMember.id(),
            logSessionId);
    }

    private boolean catchupPosition(final long leadershipTermId, final long logPosition)
    {
        return memberStatusPublisher.catchupPosition(
            leaderMember.publication(), leadershipTermId, logPosition, thisMember.id());
    }

    private void addLiveLogDestination()
    {
        final ChannelUri channelUri = followerLogDestination(ctx.logChannel(), thisMember.logEndpoint());
        liveLogDestination = channelUri.toString();

        logSubscription.addDestination(liveLogDestination);
        consensusModuleAgent.liveLogDestination(liveLogDestination);
    }

    private static ChannelUri followerLogChannel(final String logChannel, final int sessionId)
    {
        final ChannelUri channelUri = ChannelUri.parse(logChannel);
        channelUri.remove(CommonContext.MDC_CONTROL_PARAM_NAME);
        channelUri.put(CommonContext.MDC_CONTROL_MODE_PARAM_NAME, CommonContext.MDC_CONTROL_MODE_MANUAL);
        channelUri.put(CommonContext.SESSION_ID_PARAM_NAME, Integer.toString(sessionId));
        channelUri.put(CommonContext.TAGS_PARAM_NAME, ConsensusModule.Configuration.LOG_SUBSCRIPTION_TAGS);

        return channelUri;
    }

    private static ChannelUri followerLogDestination(final String logChannel, final String logEndpoint)
    {
        final ChannelUri channelUri = ChannelUri.parse(logChannel);
        channelUri.remove(CommonContext.MDC_CONTROL_PARAM_NAME);
        channelUri.put(CommonContext.ENDPOINT_PARAM_NAME, logEndpoint);

        return channelUri;
    }

    private void state(final State newState, final long nowMs)
    {
        //System.out.println("memberId=" + thisMember.id() + " nowMs=" + nowMs + " " + this.state + " -> " + newState);

        if (State.CANVASS == newState)
        {
            consensusModuleAgent.stopAllCatchups();
            ClusterMember.reset(clusterMembers);
            thisMember.leadershipTermId(leadershipTermId).logPosition(logPosition);
            consensusModuleAgent.role(Cluster.Role.FOLLOWER);
        }

        if (State.CANVASS == this.state)
        {
            isStartup = false;
        }

        if (State.CANDIDATE_BALLOT == newState)
        {
            consensusModuleAgent.role(Cluster.Role.CANDIDATE);
        }

        if (State.CANDIDATE_BALLOT == this.state && State.LEADER_REPLAY != newState)
        {
            consensusModuleAgent.role(Cluster.Role.FOLLOWER);
        }

        if (State.LEADER_TRANSITION == newState)
        {
            consensusModuleAgent.role(Cluster.Role.LEADER);
        }

        this.state = newState;
        stateCounter.setOrdered(newState.code());
        timeOfLastStateChangeMs = nowMs;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy