diff options
author | Patrick Steinhardt <psteinhardt@gitlab.com> | 2021-07-08 16:54:32 +0300 |
---|---|---|
committer | Patrick Steinhardt <psteinhardt@gitlab.com> | 2021-09-01 14:52:33 +0300 |
commit | 4df1f179d4cf1e60de941fc5d513fa496f666d52 (patch) | |
tree | b59c83fdcd8c0dc85711470ccb143908f4b9c681 | |
parent | 74ac90be1fa25803fe2020b3afe4a4f85e2731ac (diff) |
transactions: Implement function which tells whether votes did vote
It's currently not possible to tell whether a given node has cast any
vote or not. Implement it -- we'll need this as a heuristic to determine
whether the primary has been dirtied.
(cherry picked from commit 56b43c97077bbf6084ace172b22c9d7b70b9f91f)
-rw-r--r-- | internal/praefect/coordinator_test.go | 5 | ||||
-rw-r--r-- | internal/praefect/transactions/subtransaction.go | 18 | ||||
-rw-r--r-- | internal/praefect/transactions/transaction.go | 24 | ||||
-rw-r--r-- | internal/praefect/transactions/transaction_test.go | 27 |
4 files changed, 74 insertions, 0 deletions
diff --git a/internal/praefect/coordinator_test.go b/internal/praefect/coordinator_test.go index ddd32bf63..8ba453211 100644 --- a/internal/praefect/coordinator_test.go +++ b/internal/praefect/coordinator_test.go @@ -1509,6 +1509,7 @@ type mockTransaction struct { nodeStates map[string]transactions.VoteResult subtransactions int didCommitAnySubtransaction bool + didVote map[string]bool } func (t mockTransaction) ID() uint64 { @@ -1523,6 +1524,10 @@ func (t mockTransaction) DidCommitAnySubtransaction() bool { return t.didCommitAnySubtransaction } +func (t mockTransaction) DidVote(node string) bool { + return t.didVote[node] +} + func (t mockTransaction) State() (map[string]transactions.VoteResult, error) { return t.nodeStates, nil } diff --git a/internal/praefect/transactions/subtransaction.go b/internal/praefect/transactions/subtransaction.go index 7636a43eb..87b0ae1b0 100644 --- a/internal/praefect/transactions/subtransaction.go +++ b/internal/praefect/transactions/subtransaction.go @@ -345,3 +345,21 @@ func (t *subtransaction) getResult(node string) (VoteResult, error) { return voter.result, nil } + +func (t *subtransaction) getVote(node string) (*voting.Vote, error) { + t.lock.RLock() + defer t.lock.RUnlock() + + voter, ok := t.votersByNode[node] + if !ok { + return nil, fmt.Errorf("invalid node for transaction: %q", node) + } + + if voter.vote == nil { + return nil, nil + } + + // Return a copy of the vote. + vote := *voter.vote + return &vote, nil +} diff --git a/internal/praefect/transactions/transaction.go b/internal/praefect/transactions/transaction.go index 2476c0b31..c3e480ac1 100644 --- a/internal/praefect/transactions/transaction.go +++ b/internal/praefect/transactions/transaction.go @@ -59,6 +59,8 @@ type Transaction interface { State() (map[string]VoteResult, error) // DidCommitAnySubtransaction returns whether the transaction committed at least one subtransaction. DidCommitAnySubtransaction() bool + // DidVote returns whether the given node has cast a vote. + DidVote(string) bool } // transaction is a session where a set of voters votes on one or more @@ -208,6 +210,28 @@ func (t *transaction) DidCommitAnySubtransaction() bool { return false } +// DidVote determines whether the given node did cast a vote. If it's not possible to retrieve the +// vote, then the node by definition didn't cast a vote. +func (t *transaction) DidVote(node string) bool { + t.lock.Lock() + defer t.lock.Unlock() + + // If there are no subtransactions, then no vote could've been cast by the given node. + if len(t.subtransactions) == 0 { + return false + } + + // It's sufficient to take a look at the first transaction. + vote, err := t.subtransactions[0].getVote(node) + if err != nil { + // If it's not possible to retrieve the vote, then we consider the note to not have + // cast a vote. + return false + } + + return vote != nil +} + // getOrCreateSubtransaction gets an ongoing subtransaction on which the given // node hasn't yet voted on or creates a new one if the node has succeeded on // all subtransactions. In case the node has failed on any of the diff --git a/internal/praefect/transactions/transaction_test.go b/internal/praefect/transactions/transaction_test.go index 38828396a..93b636bd3 100644 --- a/internal/praefect/transactions/transaction_test.go +++ b/internal/praefect/transactions/transaction_test.go @@ -24,3 +24,30 @@ func TestTransactionCancellationWithEmptyTransaction(t *testing.T) { require.Error(t, err) require.Equal(t, err, ErrTransactionCanceled) } + +func TestTransaction_DidVote(t *testing.T) { + ctx, cleanup := testhelper.Context() + defer cleanup() + + tx, err := newTransaction(1, []Voter{ + {Name: "v1", Votes: 1}, + {Name: "v2", Votes: 0}, + }, 1) + require.NoError(t, err) + + // An unregistered voter did not vote. + require.False(t, tx.DidVote("unregistered")) + // And neither of the registered ones did cast a vote yet. + require.False(t, tx.DidVote("v1")) + require.False(t, tx.DidVote("v2")) + + // One of both nodes does cast a vote. + require.NoError(t, tx.vote(ctx, "v1", voting.VoteFromData([]byte{}))) + require.True(t, tx.DidVote("v1")) + require.False(t, tx.DidVote("v2")) + + // And now the second node does cast a vote, too. + require.NoError(t, tx.vote(ctx, "v2", voting.VoteFromData([]byte{}))) + require.True(t, tx.DidVote("v1")) + require.True(t, tx.DidVote("v2")) +} |