fix(multiplayer): propagate allocated playlist

This commit is contained in:
Josh Creek
2026-09-01 17:00:37 +01:00
parent 8bd1455a2c
commit 9ae01ecc5a
8 changed files with 24 additions and 6 deletions
+2
View File
@@ -1436,3 +1436,5 @@ The workload-authenticated `POST /servers/{serverId}/shutdown` contract is now e
The allocated supervisor now calls that shutdown acknowledgment during signal-bound controlled drain, using the same workload credential and a deterministic idempotency key after the local drain request succeeds. The lifecycle test verifies the drain-before-ack ordering, credential separation, and bounded graceful child exit; live pod termination and control-plane outage behavior remain deployment gates.
Allocator-selected region, build, protocol, and transport now travel with the allocation as Agones annotations and override stale child launch flags immediately before an allocated process starts. The overlay rejects control characters and preserves direct-server command behavior; focused supervisor/allocator tests cover precedence and annotation payloads, while live Agones passthrough remains an infrastructure gate.
The same allocation path now carries the matcher-selected playlist, preventing a ranked match from inheriting the Fleets casual default. Durable allocation claims return the playlist, the worker includes it in Fleet selection metadata, Agones copies it to the allocated GameServer, and the supervisor overrides `--playlist` before launch; the existing compatibility tests remain green.
+3
View File
@@ -177,6 +177,9 @@ func (c Client) Allocate(ctx context.Context, request domain.AllocationRequest,
"cosmic-clash.io/protocol": strconv.Itoa(request.Protocol),
"cosmic-clash.io/transport": request.Transport,
}
if playlist := labels["cosmic-clash.io/playlist"]; playlist == string(domain.Casual) || playlist == string(domain.Ranked) {
body.Spec.Metadata.Annotations["cosmic-clash.io/playlist"] = playlist
}
if len(c.WorkloadSecret) > 0 {
ttl := c.WorkloadTokenTTL
if ttl <= 0 {
+5 -1
View File
@@ -59,10 +59,14 @@ func (w Worker) RunOnce(ctx context.Context) (bool, error) {
// template. They are derived only from the durable match plan, never client
// input or mutable worker configuration.
func AllocationLabels(request domain.AllocationRequest) map[string]string {
return map[string]string{
labels := map[string]string{
"cosmic-clash.io/region": request.Region,
"cosmic-clash.io/build": request.Build,
"cosmic-clash.io/protocol": strconv.Itoa(request.Protocol),
"cosmic-clash.io/transport": request.Transport,
}
if request.Playlist != "" {
labels["cosmic-clash.io/playlist"] = string(request.Playlist)
}
return labels
}
+7
View File
@@ -85,3 +85,10 @@ func TestAllocationLabelsMirrorFleetCompatibilityTuple(t *testing.T) {
t.Fatalf("labels=%v want=%v", got, want)
}
}
func TestAllocationLabelsCarryPlaylistWhenKnown(t *testing.T) {
labels := AllocationLabels(domain.AllocationRequest{Playlist: domain.Ranked, Region: "EU", Build: "build-1", Protocol: 1, Transport: "enet"})
if labels["cosmic-clash.io/playlist"] != string(domain.Ranked) {
t.Fatalf("playlist label = %q, want %q", labels["cosmic-clash.io/playlist"], domain.Ranked)
}
}
+1
View File
@@ -27,6 +27,7 @@ type ReadyServer struct {
type AllocationRequest struct {
AllocationID string
MatchID string
Playlist Playlist
Region string
Build string
Protocol int
+4 -4
View File
@@ -30,7 +30,7 @@ UPDATE matches m
SET allocation_id = 'allocation-' || candidate.match_id, allocation_claimed_at = $2
FROM candidate
WHERE m.match_id = candidate.match_id
RETURNING m.match_id, m.region, m.protocol_version, m.allocation_id`
RETURNING m.match_id, m.playlist, m.region, m.protocol_version, m.allocation_id`
const AllocatingMatchBuildSQL = `SELECT client_build
FROM queue_tickets q
@@ -200,10 +200,10 @@ func ClaimAllocatingMatch(ctx context.Context, db *sql.DB, transport string, now
var item PendingAllocation
found := false
err := RunSerializable(ctx, db, DefaultSerializableAttempts, func(ctx context.Context, tx *sql.Tx) error {
var matchID, region string
var matchID, playlist, region string
var protocol int
var claimedID string
err := tx.QueryRowContext(ctx, ClaimAllocatingMatchSQL, now.Add(-AllocationClaimLease), now).Scan(&matchID, &region, &protocol, &claimedID)
err := tx.QueryRowContext(ctx, ClaimAllocatingMatchSQL, now.Add(-AllocationClaimLease), now).Scan(&matchID, &playlist, &region, &protocol, &claimedID)
if err == sql.ErrNoRows {
return nil
}
@@ -233,7 +233,7 @@ func ClaimAllocatingMatch(ctx context.Context, db *sql.DB, transport string, now
if build == "" {
return fmt.Errorf("allocating match has no participants")
}
item.Request = domain.AllocationRequest{AllocationID: claimedID, MatchID: matchID, Region: region, Build: build, Protocol: protocol, Transport: transport}
item.Request = domain.AllocationRequest{AllocationID: claimedID, MatchID: matchID, Playlist: domain.Playlist(playlist), Region: region, Build: build, Protocol: protocol, Transport: transport}
found = true
return nil
})
+1 -1
View File
@@ -9,7 +9,7 @@ import (
func TestAllocationMatchClaimSQLFencesConcurrentWorkers(t *testing.T) {
checks := map[string][]string{
ClaimAllocatingMatchSQL: {"FOR UPDATE SKIP LOCKED", "allocation_id = 'allocation-' || candidate.match_id", "allocation_claimed_at <= $1", "ORDER BY created_at, match_id"},
ClaimAllocatingMatchSQL: {"FOR UPDATE SKIP LOCKED", "allocation_id = 'allocation-' || candidate.match_id", "allocation_claimed_at <= $1", "ORDER BY created_at, match_id", "m.playlist"},
AllocatingMatchBuildSQL: {"match_participants", "queue_tickets", "ORDER BY q.client_build"},
BindAllocatedMatchParticipantsSQL: {"allocation_id = $2", "server_id IS NULL", "SET server_id = $3", "FROM allocations", "state = 'ALLOCATING'", "revision = revision + 1"},
ReleaseAllocatedMatchClaimSQL: {"allocation_id = $2", "allocation_id = NULL", "allocation_claimed_at = NULL"},
+1
View File
@@ -343,6 +343,7 @@ func withAllocatedCompatibility(command []string, gameServer GameServer) ([]stri
return command, nil
}
for annotation, flag := range map[string]string{
"cosmic-clash.io/playlist": "playlist",
"cosmic-clash.io/region": "region",
"cosmic-clash.io/build": "client-build",
"cosmic-clash.io/protocol": "protocol-version",