add thread-safe playback status store
This commit is contained in:
@@ -14,7 +14,7 @@ Size=200,200
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Connection]
|
[Window][Connection]
|
||||||
Pos=421,219
|
Pos=285,503
|
||||||
Size=518,191
|
Size=681,205
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package playback
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type StatusStore struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
statuses map[Unit]Status
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStatusStore() *StatusStore {
|
||||||
|
return &StatusStore{
|
||||||
|
statuses: make(map[Unit]Status),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StatusStore) Observe(status Status) {
|
||||||
|
s.mu.Lock()
|
||||||
|
s.statuses[status.Unit] = status
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StatusStore) Snapshot(unit Unit) (Status, bool) {
|
||||||
|
s.mu.RLock()
|
||||||
|
status, ok := s.statuses[unit]
|
||||||
|
s.mu.RUnlock()
|
||||||
|
return status, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StatusStore) Clear(unit Unit) {
|
||||||
|
s.mu.Lock()
|
||||||
|
delete(s.statuses, unit)
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package playback
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStatusStoreSnapshotUnknownUnit(t *testing.T) {
|
||||||
|
store := NewStatusStore()
|
||||||
|
|
||||||
|
status, ok := store.Snapshot(UnitVideo)
|
||||||
|
if ok {
|
||||||
|
t.Fatalf("Snapshot() = %#v, true; want false", status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatusStoreKeepsUnitsIndependent(t *testing.T) {
|
||||||
|
store := NewStatusStore()
|
||||||
|
wantVideo := Status{
|
||||||
|
Unit: UnitVideo,
|
||||||
|
State: StateReconnecting,
|
||||||
|
Attempt: 3,
|
||||||
|
FailedAttempts: 2,
|
||||||
|
Err: errors.New("video unavailable"),
|
||||||
|
}
|
||||||
|
wantAudio := Status{
|
||||||
|
Unit: UnitAudio,
|
||||||
|
State: StatePlaying,
|
||||||
|
Attempt: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
store.Observe(wantVideo)
|
||||||
|
store.Observe(wantAudio)
|
||||||
|
|
||||||
|
if got, ok := store.Snapshot(UnitVideo); !ok || got != wantVideo {
|
||||||
|
t.Fatalf("video Snapshot() = %#v, %t; want %#v, true", got, ok, wantVideo)
|
||||||
|
}
|
||||||
|
if got, ok := store.Snapshot(UnitAudio); !ok || got != wantAudio {
|
||||||
|
t.Fatalf("audio Snapshot() = %#v, %t; want %#v, true", got, ok, wantAudio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatusStoreObserveReplacesLatestStatus(t *testing.T) {
|
||||||
|
store := NewStatusStore()
|
||||||
|
store.Observe(Status{Unit: UnitVideo, State: StateConnecting, Attempt: 1})
|
||||||
|
want := Status{Unit: UnitVideo, State: StatePlaying, Attempt: 2}
|
||||||
|
store.Observe(want)
|
||||||
|
|
||||||
|
got, ok := store.Snapshot(UnitVideo)
|
||||||
|
if !ok || got != want {
|
||||||
|
t.Fatalf("Snapshot() = %#v, %t; want %#v, true", got, ok, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatusStoreClearOnlySelectedUnit(t *testing.T) {
|
||||||
|
store := NewStatusStore()
|
||||||
|
wantAudio := Status{Unit: UnitAudio, State: StatePlaying}
|
||||||
|
store.Observe(Status{Unit: UnitVideo, State: StatePlaying})
|
||||||
|
store.Observe(wantAudio)
|
||||||
|
|
||||||
|
store.Clear(UnitVideo)
|
||||||
|
|
||||||
|
if status, ok := store.Snapshot(UnitVideo); ok {
|
||||||
|
t.Fatalf("video Snapshot() = %#v, true after Clear", status)
|
||||||
|
}
|
||||||
|
if got, ok := store.Snapshot(UnitAudio); !ok || got != wantAudio {
|
||||||
|
t.Fatalf("audio Snapshot() = %#v, %t; want %#v, true", got, ok, wantAudio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatusStoreConcurrentAccess(t *testing.T) {
|
||||||
|
store := NewStatusStore()
|
||||||
|
const iterations = 1000
|
||||||
|
|
||||||
|
var writers sync.WaitGroup
|
||||||
|
for _, unit := range []Unit{UnitVideo, UnitAudio, UnitSync} {
|
||||||
|
unit := unit
|
||||||
|
writers.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer writers.Done()
|
||||||
|
for attempt := 1; attempt <= iterations; attempt++ {
|
||||||
|
store.Observe(Status{
|
||||||
|
Unit: unit,
|
||||||
|
State: StatePlaying,
|
||||||
|
Attempt: attempt,
|
||||||
|
})
|
||||||
|
store.Snapshot(unit)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
writers.Wait()
|
||||||
|
|
||||||
|
for _, unit := range []Unit{UnitVideo, UnitAudio, UnitSync} {
|
||||||
|
status, ok := store.Snapshot(unit)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Snapshot(%v) not found", unit)
|
||||||
|
}
|
||||||
|
if status.Attempt != iterations {
|
||||||
|
t.Fatalf(
|
||||||
|
"Snapshot(%v) attempt = %d, want %d",
|
||||||
|
unit,
|
||||||
|
status.Attempt,
|
||||||
|
iterations,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user