Pass a seat held once to its only claimant when its recorded holder is assigned nowhere (issue 1317, ADR 0449)
This commit is contained in:
@@ -153,3 +153,24 @@ func TestAReplicatedSeatHasSeveralHoldersOnRecord(t *testing.T) {
|
||||
t.Fatalf("a handover left other holders on record: %+v", held)
|
||||
}
|
||||
}
|
||||
|
||||
// A pass is a compare-and-set: it moves the record only while the holder it was read with is still on it.
|
||||
func TestPassingASeatMovesTheRecordOnceAndOnlyFromTheHolderRead(t *testing.T) {
|
||||
inv, ctx := twoBrokersOnTwoNodes(t)
|
||||
if err := inv.HoldSeat(ctx, "mesh-broker", catalogue.ScopeMesh, "anchor", "old-broker"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
from := catalogue.Held{Claim: "mesh-broker", Scope: catalogue.ScopeMesh, Node: "anchor", Module: "old-broker"}
|
||||
to := catalogue.Held{Claim: "mesh-broker", Scope: catalogue.ScopeMesh, Node: "laptop", Module: "new-broker"}
|
||||
passed, err := inv.PassSeat(ctx, "mesh-broker", catalogue.ScopeMesh, from, to)
|
||||
if err != nil || !passed {
|
||||
t.Fatalf("the seat was not passed: %v %v", passed, err)
|
||||
}
|
||||
held, _ := inv.Holdings(ctx)
|
||||
if len(held) != 1 || held[0].Node != "laptop" || held[0].Module != "new-broker" {
|
||||
t.Fatalf("the record does not name the new holder alone: %+v", held)
|
||||
}
|
||||
if passed, err = inv.PassSeat(ctx, "mesh-broker", catalogue.ScopeMesh, from, to); err != nil || passed {
|
||||
t.Fatalf("a second pass from the same read moved the record again: %v %v", passed, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,6 +284,48 @@ func (i *Inventory) HoldSeat(ctx context.Context, seat, scope, nodeName, module
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
|
||||
// PassSeat records `to` as the holder of a seat held once in place of `from`, only while `from` is still the
|
||||
// one on record: a compare-and-set in one transaction, so two compositions reading the same record cannot both
|
||||
// hand the seat over (novox/hq ADR 0449 §4). It answers false, with no error, when the record no longer names
|
||||
// `from` (another composition passed it first). The new holder must be an assignment.
|
||||
func (i *Inventory) PassSeat(ctx context.Context, seat, scope string, from, to catalogue.Held) (bool, error) {
|
||||
fromNode, err := i.NodeByName(ctx, from.Node)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
toNode, err := i.NodeByName(ctx, to.Node)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := i.mustBeAssigned(ctx, seat, to.Node, to.Module); err != nil {
|
||||
return false, err
|
||||
}
|
||||
tx, err := i.store.Pool().Begin(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func() { _ = tx.Rollback(context.WithoutCancel(ctx)) }()
|
||||
gone, err := tx.Exec(ctx,
|
||||
`delete from seat_holding where seat = $1 and node = $2 and module = $3`,
|
||||
seat, fromNode.ID, from.Module)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("passing %s from %s on %s: %w", seat, from.Module, from.Node, err)
|
||||
}
|
||||
if gone.RowsAffected() == 0 {
|
||||
return false, nil
|
||||
}
|
||||
if _, err := tx.Exec(ctx,
|
||||
`delete from seat_holding where seat = $1`, seat); err != nil {
|
||||
return false, fmt.Errorf("passing %s: %w", seat, err)
|
||||
}
|
||||
if _, err := tx.Exec(ctx,
|
||||
`insert into seat_holding (seat, scope, node, module) values ($1, $2, $3, $4)`,
|
||||
seat, scope, toNode.ID, to.Module); err != nil {
|
||||
return false, fmt.Errorf("recording %s on %s as the holder of %s: %w", to.Module, to.Node, seat, err)
|
||||
}
|
||||
return true, tx.Commit(ctx)
|
||||
}
|
||||
|
||||
// AddSeatHolder records one more assignment holding a replicated seat, beside those already on
|
||||
// record (novox/hq ADR 0223). Whether the seat may have several holders is the caller's to judge —
|
||||
// the seat's definition is compiled, and the store holds no column for it. Recording a holder
|
||||
|
||||
Reference in New Issue
Block a user