Files
CosmicClash/server/allocator/worker.go
T
2026-09-01 17:00:37 +01:00

73 lines
2.5 KiB
Go

package allocator
import (
"context"
"fmt"
"strconv"
"time"
"github.com/cosmic-clash/cosmic-clash/server/domain"
)
// MatchClaimSource is the durable allocator work queue. Implementations must
// lease a match before returning it and fence binding by allocation ID.
type MatchClaimSource interface {
ClaimAllocatingMatch(context.Context, time.Time) (domain.AllocationRequest, bool, error)
FindProviderAllocation(context.Context, domain.AllocationRequest) (domain.Allocation, bool, error)
BindAllocatedMatch(context.Context, domain.Allocation) error
}
// Worker consumes one leased match at a time. Provider failures deliberately
// retain the lease: an HTTP/provider failure can be ambiguous after an external
// allocation, so releasing it could allocate two GameServers for one match.
type Worker struct {
Claims MatchClaimSource
Service Service
Now func() time.Time
}
// RunOnce returns whether it found a claimed match. It never exposes an
// endpoint itself; Service first records the provider allocation durably and
// BindAllocatedMatch then attaches that already-recorded allocation to the
// fenced match claim.
func (w Worker) RunOnce(ctx context.Context) (bool, error) {
if w.Claims == nil || w.Now == nil {
return false, errNotConfigured
}
request, found, err := w.Claims.ClaimAllocatingMatch(ctx, w.Now())
if err != nil || !found {
return found, err
}
allocation, recorded, err := w.Claims.FindProviderAllocation(ctx, request)
if err != nil {
return true, fmt.Errorf("recover allocation for match %s: %w", request.MatchID, err)
}
if !recorded {
result, err := w.Service.Allocate(ctx, request, AllocationLabels(request))
if err != nil {
return true, fmt.Errorf("allocate claimed match %s: %w", request.MatchID, err)
}
allocation = result.Allocation
}
if err := w.Claims.BindAllocatedMatch(ctx, allocation); err != nil {
return true, fmt.Errorf("bind allocated match %s: %w", request.MatchID, err)
}
return true, nil
}
// AllocationLabels are the compatibility selectors shared with the Fleet
// 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 {
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
}