private static async Task <Result <VotingResult> > VoteAsync(IRaftClusterMember voter, long term, IAuditTrail <IRaftLogEntry> auditTrail, CancellationToken token)
            {
                var lastIndex = auditTrail.GetLastIndex(false);
                var lastTerm  = await auditTrail.GetTermAsync(lastIndex, token).ConfigureAwait(false);

                VotingResult result;

                try
                {
                    var response = await voter.VoteAsync(term, lastIndex, lastTerm, token).ConfigureAwait(false);

                    term   = response.Term;
                    result = response.Value ? VotingResult.Granted : VotingResult.Rejected;
                }
                catch (OperationCanceledException)
                {
                    result = VotingResult.Canceled;
                }
                catch (MemberUnavailableException)
                {
                    result = VotingResult.NotAvailable;
                    term   = -1L;
                }

                return(new Result <VotingResult>(term, result));
            }
示例#2
0
        //true if at least one entry from current term is stored on this node; otherwise, false
        private static async Task <Result <bool> > AppendEntriesAsync(IRaftClusterMember member, long commitIndex,
                                                                      long term,
                                                                      IAuditTrail <ILogEntry> transactionLog, ILogger logger, CancellationToken token)
        {
            var currentIndex = transactionLog.GetLastIndex(false);

            logger.ReplicationStarted(member.Endpoint, currentIndex);
            var precedingIndex = Math.Max(0, member.NextIndex - 1);
            var precedingTerm  = (await transactionLog.GetEntryAsync(precedingIndex).ConfigureAwait(false) ??
                                  transactionLog.First).Term;
            var entries = currentIndex >= member.NextIndex
                ? await transactionLog.GetEntriesAsync(member.NextIndex).ConfigureAwait(false)
                : Array.Empty <ILogEntry>();

            logger.ReplicaSize(member.Endpoint, entries.Count, precedingIndex, precedingTerm);
            //trying to replicate
            var result = await member
                         .AppendEntriesAsync(term, entries, precedingIndex, precedingTerm, commitIndex, token)
                         .ConfigureAwait(false);

            if (result.Value)
            {
                logger.ReplicationSuccessful(member.Endpoint, member.NextIndex);
                member.NextIndex.VolatileWrite(currentIndex + 1);
                //checks whether the at least one entry from current term is stored on this node
                result = result.SetValue(entries.Any(entry => entry.Term == term));
            }
            else
            {
                logger.ReplicationFailed(member.Endpoint, member.NextIndex.DecrementAndGet());
            }

            return(result);
        }
示例#3
0
 internal static async Task WaitForCommitAsync(IAuditTrail log, IAsyncEvent commitEvent, long index, TimeSpan timeout, CancellationToken token)
 {
     for (var timeoutMeasurement = new Timeout(timeout); log.GetLastIndex(true) < index; await commitEvent.Wait(timeout, token).ConfigureAwait(false))
     {
         timeoutMeasurement.ThrowIfExpired(out timeout);
     }
 }
示例#4
0
        internal static async ValueTask <bool> IsUpToDateAsync(this IAuditTrail <ILogEntry> auditTrail, long index, long term)
        {
            var localIndex = auditTrail.GetLastIndex(false);
            var localTerm  = (await auditTrail.GetEntryAsync(localIndex).ConfigureAwait(false) ?? auditTrail.First)
                             .Term;

            return(index >= localIndex && term >= localTerm);
        }
示例#5
0
 /// <summary>
 /// Starts cluster synchronization.
 /// </summary>
 /// <param name="period">Time period of Heartbeats.</param>
 /// <param name="transactionLog">Transaction log.</param>
 /// <param name="token">The toke that can be used to cancel the operation.</param>
 internal LeaderState StartLeading(TimeSpan period, IAuditTrail <IRaftLogEntry> transactionLog, CancellationToken token)
 {
     foreach (var member in stateMachine.Members)
     {
         member.NextIndex = transactionLog.GetLastIndex(false) + 1;
     }
     heartbeatTask = DoHeartbeats(period, transactionLog, token);
     return(this);
 }
示例#6
0
            internal ValueTask <Result <bool> > Start(IAuditTrail <IRaftLogEntry> auditTrail)
            {
                var currentIndex = this.currentIndex = auditTrail.GetLastIndex(false);

                logger.ReplicationStarted(member.Endpoint, currentIndex);
                return(currentIndex >= member.NextIndex ?
                       auditTrail.ReadAsync <Replicator, Result <bool> >(this, member.NextIndex, token) :
                       ReadAsync <IRaftLogEntry, IRaftLogEntry[]>(Array.Empty <IRaftLogEntry>(), null, token));
            }
示例#7
0
        private async Task <bool> DoHeartbeats(IAuditTrail <ILogEntry> transactionLog)
        {
            ICollection <Task <Result <MemberHealthStatus> > > tasks = new LinkedList <Task <Result <MemberHealthStatus> > >();
            //send heartbeat in parallel
            var commitIndex = transactionLog.GetLastIndex(true);

            foreach (var member in stateMachine.Members)
            {
                if (member.IsRemote)
                {
                    tasks.Add(AppendEntriesAsync(member, commitIndex, currentTerm, transactionLog, stateMachine.Logger, timerCancellation.Token)
                              .ContinueWith(HealthStatusContinuation, default, TaskContinuationOptions.ExecuteSynchronously,
示例#8
0
        private async Task <bool> DoHeartbeats(IAuditTrail <IRaftLogEntry> auditTrail, CancellationToken token)
        {
            var timeStamp = Timestamp.Current;
            var tasks     = new LinkedList <ValueTask <Result <bool> > >();

            long commitIndex = auditTrail.GetLastIndex(true), currentIndex = auditTrail.GetLastIndex(false);
            var  term = currentTerm;

            //send heartbeat in parallel
            foreach (var member in stateMachine.Members)
            {
                if (member.IsRemote)
                {
                    long precedingIndex = Math.Max(0, member.NextIndex - 1), precedingTerm = await auditTrail.GetTermAsync(precedingIndex, token);

                    tasks.AddLast(new Replicator(member, commitIndex, currentIndex, term, precedingIndex, precedingTerm, stateMachine.Logger, token).Start(auditTrail));
                }
            }
            var quorum       = 1; //because we know that the entry is replicated in this node
            var commitQuorum = 1;

            for (var task = tasks.First; task != null; task.Value = default, task = task.Next)
            {
                try
                {
                    var result = await task.Value.ConfigureAwait(false);

                    term          = Math.Max(term, result.Term);
                    quorum       += 1;
                    commitQuorum += result.Value ? 1 : -1;
                }
                catch (MemberUnavailableException)
                {
                    quorum       -= 1;
                    commitQuorum -= 1;
                }
            }
示例#9
0
 internal static async Task <bool> ContainsAsync(this IAuditTrail <IRaftLogEntry> auditTrail, long index, long term, CancellationToken token)
 => index <= auditTrail.GetLastIndex(false) && term == await auditTrail.GetTermAsync(index, token).ConfigureAwait(false);
示例#10
0
        internal static async Task <bool> IsUpToDateAsync(this IAuditTrail <IRaftLogEntry> auditTrail, long index, long term, CancellationToken token)
        {
            var localIndex = auditTrail.GetLastIndex(false);

            return(index >= localIndex && term >= await auditTrail.GetTermAsync(localIndex, token).ConfigureAwait(false));
        }
示例#11
0
        private async Task <bool> DoHeartbeats(IAuditTrail <IRaftLogEntry> auditTrail, CancellationToken token)
        {
            var timeStamp = Timestamp.Current;
            var tasks     = new AsyncResultSet();

            long commitIndex       = auditTrail.GetLastIndex(true),
                 currentIndex      = auditTrail.GetLastIndex(false),
                 term              = currentTerm,
                 minPrecedingIndex = 0L;

            // send heartbeat in parallel
            foreach (var member in stateMachine.Members)
            {
                if (member.IsRemote)
                {
                    long precedingIndex = Math.Max(0, member.NextIndex - 1), precedingTerm;
                    minPrecedingIndex = Math.Min(minPrecedingIndex, precedingIndex);

                    // try to get term from the cache to avoid touching audit trail for each member
                    if (!precedingTermCache.TryGetValue(precedingIndex, out precedingTerm))
                    {
                        precedingTermCache.Add(precedingIndex, precedingTerm = await auditTrail.GetTermAsync(precedingIndex, token).ConfigureAwait(false));
                    }

                    tasks.AddLast(new Replicator(member, commitIndex, currentIndex, term, precedingIndex, precedingTerm, stateMachine.Logger, token).Start(auditTrail));
                }
            }

            // clear cache
            if (precedingTermCache.Count > MaxTermCacheSize)
            {
                precedingTermCache.Clear();
            }
            else
            {
                precedingTermCache.RemoveHead(minPrecedingIndex);
            }

            int quorum = 1, commitQuorum = 1; // because we know that the entry is replicated in this node

#if NETSTANDARD2_1
            for (var task = tasks.First; task is not null; task.Value = default, task = task.Next)
#else
            for (var task = tasks.First; task is not null; task.ValueRef = default, task = task.Next)
#endif
            {
                try
                {
#if NETSTANDARD2_1
                    var result = await task.Value.ConfigureAwait(false);
#else
                    var result = await task.ValueRef.ConfigureAwait(false);
#endif
                    term          = Math.Max(term, result.Term);
                    quorum       += 1;
                    commitQuorum += result.Value ? 1 : -1;
                }
                catch (MemberUnavailableException)
                {
                    quorum       -= 1;
                    commitQuorum -= 1;
                }
                catch (OperationCanceledException)
                {
                    // leading was canceled
                    tasks.Clear();
                    Metrics?.ReportBroadcastTime(timeStamp.Elapsed);
                    return(false);
                }
                catch (Exception e)
                {
                    stateMachine.Logger.LogError(e, ExceptionMessages.UnexpectedError);
                }
            }

            tasks.Clear();
            Metrics?.ReportBroadcastTime(timeStamp.Elapsed);

            // majority of nodes accept entries with a least one entry from the current term
            if (commitQuorum > 0)
            {
                var count = await auditTrail.CommitAsync(currentIndex, token).ConfigureAwait(false); // commit all entries started from first uncommitted index to the end

                stateMachine.Logger.CommitSuccessful(commitIndex + 1, count);
                goto check_term;
            }

            stateMachine.Logger.CommitFailed(quorum, commitIndex);

            // majority of nodes replicated, continue leading if current term is not changed
            if (quorum <= 0 && !allowPartitioning)
            {
                goto stop_leading;
            }

check_term:
            if (term <= currentTerm)
            {
                return(true);
            }

            // it is partitioned network with absolute majority, not possible to have more than one leader
stop_leading:
            stateMachine.MoveToFollowerState(false, term);
            return(false);
        }
示例#12
0
        private async Task <bool> DoHeartbeats(IAuditTrail <IRaftLogEntry> auditTrail, CancellationToken token)
        {
            var timeStamp = Timestamp.Current;
            var tasks     = new LinkedList <ValueTask <Result <bool> > >();

            long commitIndex = auditTrail.GetLastIndex(true), currentIndex = auditTrail.GetLastIndex(false);
            var  term = currentTerm;

            // send heartbeat in parallel
            foreach (var member in stateMachine.Members)
            {
                if (member.IsRemote)
                {
                    long precedingIndex = Math.Max(0, member.NextIndex - 1), precedingTerm = await auditTrail.GetTermAsync(precedingIndex, token).ConfigureAwait(false);

                    tasks.AddLast(new Replicator(member, commitIndex, currentIndex, term, precedingIndex, precedingTerm, stateMachine.Logger, token).Start(auditTrail));
                }
            }

            var quorum       = 1; // because we know that the entry is replicated in this node
            var commitQuorum = 1;

            for (var task = tasks.First; task != null; task.Value = default, task = task.Next)
            {
                try
                {
                    var result = await task.Value.ConfigureAwait(false);

                    term          = Math.Max(term, result.Term);
                    quorum       += 1;
                    commitQuorum += result.Value ? 1 : -1;
                }
                catch (MemberUnavailableException)
                {
                    quorum       -= 1;
                    commitQuorum -= 1;
                }
                catch (OperationCanceledException)
                {
                    // leading was canceled
                    tasks.Clear();
                    Metrics?.ReportBroadcastTime(timeStamp.Elapsed);
                    return(false);
                }
                catch (Exception e)
                {
                    stateMachine.Logger.LogError(e, ExceptionMessages.UnexpectedError);
                }
            }

            tasks.Clear();
            Metrics?.ReportBroadcastTime(timeStamp.Elapsed);

            // majority of nodes accept entries with a least one entry from current term
            if (commitQuorum > 0)
            {
                var count = await auditTrail.CommitAsync(currentIndex, token).ConfigureAwait(false); // commit all entries started from first uncommitted index to the end

                stateMachine.Logger.CommitSuccessful(commitIndex + 1, count);
                return(CheckTerm(term));
            }

            stateMachine.Logger.CommitFailed(quorum, commitIndex);

            // majority of nodes replicated, continue leading if current term is not changed
            if (quorum > 0 || allowPartitioning)
            {
                return(CheckTerm(term));
            }

            // it is partitioned network with absolute majority, not possible to have more than one leader
            stateMachine.MoveToFollowerState(false, term);
            return(false);
        }