Mint an assignment's account once, whoever finds it missing first (issue 1945, ADR 0543, review)
The assign act and the assignments' watch both checked whether an account was minted and then minted it, and minting overwrites the hash: interleaved, the bus kept one caller's hash and the node was sealed the other's password, the crash loop this branch fixes. Both now mint through one statement that inserts the row only when none stands; the caller whose insert wins seals, the other seals nothing. issueMissingProcessAccounts mints the same way. A watch opened again after one ended handed the standing assignments as the start's look, so one that came to stand in the gap got no bus composition or grant when its account already stood: only the process's first watch is the start's look now. The start's look reads the catalogue once and ends with one composition of the bus's node and the grants; a controller not holding the lease is told so and tries again rather than dropping the key. Tests folded to stay within the budget: the assignment tests are three table tests, the process account and mayIssue tests folded into one of them, and the concurrent mint is a case of the inventory's rotation test. Research 072: the grants piece (memberships and credentials heard from placement).
This commit is contained in:
@@ -33,7 +33,7 @@ func TestABusPasswordIsReturnedOnceAndOnlyItsHashIsKept(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMintingAgainRotatesRatherThanAddsAUser(t *testing.T) {
|
||||
func TestMintingAgainRotatesAndMintingIfAbsentHasOneWinner(t *testing.T) {
|
||||
inv := ForTest(t)
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -62,6 +62,50 @@ func TestMintingAgainRotatesRatherThanAddsAUser(t *testing.T) {
|
||||
if bcrypt.CompareHashAndPassword([]byte(hash), []byte(first)) == nil {
|
||||
t.Fatal("the previous password still verifies, so a rotation revoked nothing")
|
||||
}
|
||||
|
||||
// **Minting only if absent never rotates, and of several at once exactly one wins** (ADR 0543, the review of issue
|
||||
// 1945): the assign act and the assignments' watch may both find an account missing; the winner's password is the
|
||||
// one the store's hash verifies, so the one it seals is the one the bus accepts, and a loser seals nothing.
|
||||
if _, won, err := inv.MintBusPasswordIfAbsent(ctx, aBusUser("shop")); err != nil || won {
|
||||
t.Fatalf("a mint-if-absent over a minted account won %v (%v): it would replace a sealed password", won, err)
|
||||
}
|
||||
const racers = 8
|
||||
type minted struct {
|
||||
password string
|
||||
won bool
|
||||
err error
|
||||
}
|
||||
results := make(chan minted, racers)
|
||||
for range racers {
|
||||
go func() {
|
||||
p, won, err := inv.MintBusPasswordIfAbsent(ctx, aBusUser("racer"))
|
||||
results <- minted{p, won, err}
|
||||
}()
|
||||
}
|
||||
var winner string
|
||||
wins := 0
|
||||
for range racers {
|
||||
r := <-results
|
||||
if r.err != nil {
|
||||
t.Fatal(r.err)
|
||||
}
|
||||
if r.won {
|
||||
wins++
|
||||
winner = r.password
|
||||
} else if r.password != "" {
|
||||
t.Fatal("a caller that lost was handed a password to seal")
|
||||
}
|
||||
}
|
||||
if wins != 1 {
|
||||
t.Fatalf("%d of %d concurrent mints won, want exactly one", wins, racers)
|
||||
}
|
||||
stored, _, err := inv.BusUserHash(ctx, "one.racer")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(stored), []byte(winner)); err != nil {
|
||||
t.Fatal("the stored hash is not the winner's password: the node would be sealed a password the bus refuses")
|
||||
}
|
||||
}
|
||||
|
||||
func TestForgettingANodeTakesItsBusUsersWithIt(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user