175 lines
4.2 KiB
Go
175 lines
4.2 KiB
Go
package runtime
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// Cache is the in-memory write-through projection of the runtime
|
|
// records and engine version registry. Mirrors the lobby/auth/admin
|
|
// cache idiom: Postgres is the source of truth, the cache is updated
|
|
// only after a successful commit.
|
|
//
|
|
// Reads (Get*) take RLocks; writes (Put*, Remove*) take Locks. The
|
|
// cache only retains non-terminal runtime records so the active set
|
|
// stays small and warm.
|
|
type Cache struct {
|
|
mu sync.RWMutex
|
|
runtimes map[uuid.UUID]RuntimeRecord
|
|
engineVersions map[string]EngineVersion
|
|
ready atomic.Bool
|
|
}
|
|
|
|
// NewCache returns an empty Cache.
|
|
func NewCache() *Cache {
|
|
return &Cache{
|
|
runtimes: make(map[uuid.UUID]RuntimeRecord),
|
|
engineVersions: make(map[string]EngineVersion),
|
|
}
|
|
}
|
|
|
|
// Warm populates the cache from store. Must be called once at process
|
|
// boot before the HTTP listener accepts traffic.
|
|
func (c *Cache) Warm(ctx context.Context, store *Store) error {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
versions, err := store.ListEngineVersions(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("runtime cache warm: engine versions: %w", err)
|
|
}
|
|
records, err := store.ListAllRuntimeRecords(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("runtime cache warm: runtime records: %w", err)
|
|
}
|
|
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.engineVersions = make(map[string]EngineVersion, len(versions))
|
|
for _, v := range versions {
|
|
c.engineVersions[v.Version] = v
|
|
}
|
|
c.runtimes = make(map[uuid.UUID]RuntimeRecord)
|
|
for _, r := range records {
|
|
if r.IsTerminal() {
|
|
continue
|
|
}
|
|
c.runtimes[r.GameID] = r
|
|
}
|
|
c.ready.Store(true)
|
|
return nil
|
|
}
|
|
|
|
// Ready reports whether Warm completed at least once.
|
|
func (c *Cache) Ready() bool {
|
|
if c == nil {
|
|
return false
|
|
}
|
|
return c.ready.Load()
|
|
}
|
|
|
|
// Sizes returns the cardinalities of the two projections; used by the
|
|
// startup log line and tests.
|
|
func (c *Cache) Sizes() (runtimes int, engineVersions int) {
|
|
if c == nil {
|
|
return 0, 0
|
|
}
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
return len(c.runtimes), len(c.engineVersions)
|
|
}
|
|
|
|
// GetRuntime returns the cached runtime record for gameID together
|
|
// with a presence flag.
|
|
func (c *Cache) GetRuntime(gameID uuid.UUID) (RuntimeRecord, bool) {
|
|
if c == nil {
|
|
return RuntimeRecord{}, false
|
|
}
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
r, ok := c.runtimes[gameID]
|
|
return r, ok
|
|
}
|
|
|
|
// PutRuntime stores or updates the runtime record. Terminal statuses
|
|
// cause the entry to be evicted.
|
|
func (c *Cache) PutRuntime(rec RuntimeRecord) {
|
|
if c == nil {
|
|
return
|
|
}
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
if rec.IsTerminal() {
|
|
delete(c.runtimes, rec.GameID)
|
|
return
|
|
}
|
|
c.runtimes[rec.GameID] = rec
|
|
}
|
|
|
|
// RemoveRuntime evicts the entry for gameID.
|
|
func (c *Cache) RemoveRuntime(gameID uuid.UUID) {
|
|
if c == nil {
|
|
return
|
|
}
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
delete(c.runtimes, gameID)
|
|
}
|
|
|
|
// ActiveRuntimes returns a snapshot copy of every cached runtime
|
|
// record. The reconciler and the scheduler both iterate this list.
|
|
func (c *Cache) ActiveRuntimes() []RuntimeRecord {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
out := make([]RuntimeRecord, 0, len(c.runtimes))
|
|
for _, r := range c.runtimes {
|
|
out = append(out, r)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// GetEngineVersion returns the cached engine_versions row keyed by
|
|
// version label, together with a presence flag.
|
|
func (c *Cache) GetEngineVersion(version string) (EngineVersion, bool) {
|
|
if c == nil {
|
|
return EngineVersion{}, false
|
|
}
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
v, ok := c.engineVersions[version]
|
|
return v, ok
|
|
}
|
|
|
|
// PutEngineVersion stores or updates the engine_versions cache entry.
|
|
func (c *Cache) PutEngineVersion(v EngineVersion) {
|
|
if c == nil {
|
|
return
|
|
}
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.engineVersions[v.Version] = v
|
|
}
|
|
|
|
// ListEngineVersions returns a snapshot of the cached engine_versions
|
|
// rows ordered by created_at DESC. Falls back to a deterministic order
|
|
// by version label when timestamps tie.
|
|
func (c *Cache) ListEngineVersions() []EngineVersion {
|
|
if c == nil {
|
|
return nil
|
|
}
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
out := make([]EngineVersion, 0, len(c.engineVersions))
|
|
for _, v := range c.engineVersions {
|
|
out = append(out, v)
|
|
}
|
|
return out
|
|
}
|