| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 | 
							- // Copyright 2019 The etcd Authors
 
- //
 
- // 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 quorum
 
- import (
 
- 	"fmt"
 
- 	"math"
 
- 	"sort"
 
- 	"strings"
 
- )
 
- // MajorityConfig is a set of IDs that uses majority quorums to make decisions.
 
- type MajorityConfig map[uint64]struct{}
 
- func (c MajorityConfig) String() string {
 
- 	sl := make([]uint64, 0, len(c))
 
- 	for id := range c {
 
- 		sl = append(sl, id)
 
- 	}
 
- 	sort.Slice(sl, func(i, j int) bool { return sl[i] < sl[j] })
 
- 	var buf strings.Builder
 
- 	buf.WriteByte('(')
 
- 	for i := range sl {
 
- 		if i > 0 {
 
- 			buf.WriteByte(' ')
 
- 		}
 
- 		fmt.Fprint(&buf, sl[i])
 
- 	}
 
- 	buf.WriteByte(')')
 
- 	return buf.String()
 
- }
 
- // Describe returns a (multi-line) representation of the commit indexes for the
 
- // given lookuper.
 
- func (c MajorityConfig) Describe(l AckedIndexer) string {
 
- 	if len(c) == 0 {
 
- 		return "<empty majority quorum>"
 
- 	}
 
- 	type tup struct {
 
- 		id  uint64
 
- 		idx Index
 
- 		ok  bool // idx found?
 
- 		bar int  // length of bar displayed for this tup
 
- 	}
 
- 	// Below, populate .bar so that the i-th largest commit index has bar i (we
 
- 	// plot this as sort of a progress bar). The actual code is a bit more
 
- 	// complicated and also makes sure that equal index => equal bar.
 
- 	n := len(c)
 
- 	info := make([]tup, 0, n)
 
- 	for id := range c {
 
- 		idx, ok := l.AckedIndex(id)
 
- 		info = append(info, tup{id: id, idx: idx, ok: ok})
 
- 	}
 
- 	// Sort by index
 
- 	sort.Slice(info, func(i, j int) bool {
 
- 		if info[i].idx == info[j].idx {
 
- 			return info[i].id < info[j].id
 
- 		}
 
- 		return info[i].idx < info[j].idx
 
- 	})
 
- 	// Populate .bar.
 
- 	for i := range info {
 
- 		if i > 0 && info[i-1].idx < info[i].idx {
 
- 			info[i].bar = i
 
- 		}
 
- 	}
 
- 	// Sort by ID.
 
- 	sort.Slice(info, func(i, j int) bool {
 
- 		return info[i].id < info[j].id
 
- 	})
 
- 	var buf strings.Builder
 
- 	// Print.
 
- 	fmt.Fprint(&buf, strings.Repeat(" ", n)+"    idx\n")
 
- 	for i := range info {
 
- 		bar := info[i].bar
 
- 		if !info[i].ok {
 
- 			fmt.Fprint(&buf, "?"+strings.Repeat(" ", n))
 
- 		} else {
 
- 			fmt.Fprint(&buf, strings.Repeat("x", bar)+">"+strings.Repeat(" ", n-bar))
 
- 		}
 
- 		fmt.Fprintf(&buf, " %5d    (id=%d)\n", info[i].idx, info[i].id)
 
- 	}
 
- 	return buf.String()
 
- }
 
- // Slice returns the MajorityConfig as a sorted slice.
 
- func (c MajorityConfig) Slice() []uint64 {
 
- 	var sl []uint64
 
- 	for id := range c {
 
- 		sl = append(sl, id)
 
- 	}
 
- 	sort.Slice(sl, func(i, j int) bool { return sl[i] < sl[j] })
 
- 	return sl
 
- }
 
- func insertionSort(sl []uint64) {
 
- 	a, b := 0, len(sl)
 
- 	for i := a + 1; i < b; i++ {
 
- 		for j := i; j > a && sl[j] < sl[j-1]; j-- {
 
- 			sl[j], sl[j-1] = sl[j-1], sl[j]
 
- 		}
 
- 	}
 
- }
 
- // CommittedIndex computes the committed index from those supplied via the
 
- // provided AckedIndexer (for the active config).
 
- func (c MajorityConfig) CommittedIndex(l AckedIndexer) Index {
 
- 	n := len(c)
 
- 	if n == 0 {
 
- 		// This plays well with joint quorums which, when one half is the zero
 
- 		// MajorityConfig, should behave like the other half.
 
- 		return math.MaxUint64
 
- 	}
 
- 	// Use an on-stack slice to collect the committed indexes when n <= 7
 
- 	// (otherwise we alloc). The alternative is to stash a slice on
 
- 	// MajorityConfig, but this impairs usability (as is, MajorityConfig is just
 
- 	// a map, and that's nice). The assumption is that running with a
 
- 	// replication factor of >7 is rare, and in cases in which it happens
 
- 	// performance is a lesser concern (additionally the performance
 
- 	// implications of an allocation here are far from drastic).
 
- 	var stk [7]uint64
 
- 	var srt []uint64
 
- 	if len(stk) >= n {
 
- 		srt = stk[:n]
 
- 	} else {
 
- 		srt = make([]uint64, n)
 
- 	}
 
- 	{
 
- 		// Fill the slice with the indexes observed. Any unused slots will be
 
- 		// left as zero; these correspond to voters that may report in, but
 
- 		// haven't yet. We fill from the right (since the zeroes will end up on
 
- 		// the left after sorting below anyway).
 
- 		i := n - 1
 
- 		for id := range c {
 
- 			if idx, ok := l.AckedIndex(id); ok {
 
- 				srt[i] = uint64(idx)
 
- 				i--
 
- 			}
 
- 		}
 
- 	}
 
- 	// Sort by index. Use a bespoke algorithm (copied from the stdlib's sort
 
- 	// package) to keep srt on the stack.
 
- 	insertionSort(srt)
 
- 	// The smallest index into the array for which the value is acked by a
 
- 	// quorum. In other words, from the end of the slice, move n/2+1 to the
 
- 	// left (accounting for zero-indexing).
 
- 	pos := n - (n/2 + 1)
 
- 	return Index(srt[pos])
 
- }
 
- // VoteResult takes a mapping of voters to yes/no (true/false) votes and returns
 
- // a result indicating whether the vote is pending (i.e. neither a quorum of
 
- // yes/no has been reached), won (a quorum of yes has been reached), or lost (a
 
- // quorum of no has been reached).
 
- func (c MajorityConfig) VoteResult(votes map[uint64]bool) VoteResult {
 
- 	if len(c) == 0 {
 
- 		// By convention, the elections on an empty config win. This comes in
 
- 		// handy with joint quorums because it'll make a half-populated joint
 
- 		// quorum behave like a majority quorum.
 
- 		return VoteWon
 
- 	}
 
- 	ny := [2]int{} // vote counts for no and yes, respectively
 
- 	var missing int
 
- 	for id := range c {
 
- 		v, ok := votes[id]
 
- 		if !ok {
 
- 			missing++
 
- 			continue
 
- 		}
 
- 		if v {
 
- 			ny[1]++
 
- 		} else {
 
- 			ny[0]++
 
- 		}
 
- 	}
 
- 	q := len(c)/2 + 1
 
- 	if ny[1] >= q {
 
- 		return VoteWon
 
- 	}
 
- 	if ny[1]+missing >= q {
 
- 		return VotePending
 
- 	}
 
- 	return VoteLost
 
- }
 
 
  |