mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 08:23:45 +00:00
feat: publish assignment rosters atomically
This commit is contained in:
@@ -86,6 +86,44 @@ func SaveAssignment(ctx context.Context, db *sql.DB, assignment DurableAssignmen
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveAssignments publishes a complete signed roster atomically. Assignment
|
||||
// readiness is a match boundary: exposing only some players would let the
|
||||
// control plane tell different participants incompatible stories after a
|
||||
// transient database failure.
|
||||
func SaveAssignments(ctx context.Context, db *sql.DB, assignments []DurableAssignment) error {
|
||||
if db == nil || len(assignments) == 0 {
|
||||
return fmt.Errorf("invalid assignment batch")
|
||||
}
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
seen := make(map[string]struct{}, len(assignments))
|
||||
for _, assignment := range assignments {
|
||||
if err := validateDurableAssignment(assignment); err != nil {
|
||||
return err
|
||||
}
|
||||
key := assignment.MatchID + "\x00" + assignment.PlayerID
|
||||
if _, ok := seen[key]; ok {
|
||||
return fmt.Errorf("duplicate assignment in batch")
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
result, err := tx.ExecContext(ctx, AssignmentUpsertSQL, assignment.MatchID, assignment.PlayerID, assignment.AllocationID, assignment.ServerID, assignment.Slot, assignment.Region, assignment.ClientBuild, assignment.ProtocolVersion, assignment.Transport, assignment.Endpoint, assignment.JoinAuthorisation, assignment.ManifestDigest, assignment.ExpiresAt, assignment.Revision)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
changed, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if changed != 1 {
|
||||
return fmt.Errorf("assignment persistence conflict")
|
||||
}
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func GetAssignment(ctx context.Context, db *sql.DB, playerID, matchID string, now time.Time) (DurableAssignment, error) {
|
||||
if db == nil || playerID == "" || matchID == "" || now.IsZero() {
|
||||
return DurableAssignment{}, fmt.Errorf("invalid assignment recovery arguments")
|
||||
|
||||
@@ -29,3 +29,12 @@ func TestAssignmentStoreRejectsInvalidRecoveryAndManifestInputs(t *testing.T) {
|
||||
t.Fatal("out-of-range slot accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignmentStoreRejectsInvalidBatches(t *testing.T) {
|
||||
if err := SaveAssignments(nil, nil, nil); err == nil {
|
||||
t.Fatal("nil database/empty batch accepted")
|
||||
}
|
||||
if err := SaveAssignments(nil, nil, []DurableAssignment{{MatchID: "match-1", PlayerID: "player-1"}}); err == nil {
|
||||
t.Fatal("invalid assignment batch accepted")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user