generation-aware playback statuses
This commit is contained in:
@@ -72,7 +72,8 @@ func (s *stabilityAudioSink) ConsumeAudio(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *AudioWorker) emit(status Status) {
|
func (w *AudioWorker) emit(ctx context.Context, status Status) {
|
||||||
|
status.Generation = generationFromContext(ctx)
|
||||||
if w.observer != nil {
|
if w.observer != nil {
|
||||||
w.observer(status)
|
w.observer(status)
|
||||||
}
|
}
|
||||||
@@ -99,7 +100,7 @@ func (w *AudioWorker) Run(
|
|||||||
if attemptNumber > 1 {
|
if attemptNumber > 1 {
|
||||||
state = StateReconnecting
|
state = StateReconnecting
|
||||||
}
|
}
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitAudio,
|
Unit: UnitAudio,
|
||||||
State: state,
|
State: state,
|
||||||
Attempt: attemptNumber,
|
Attempt: attemptNumber,
|
||||||
@@ -108,7 +109,7 @@ func (w *AudioWorker) Run(
|
|||||||
attemptSink := &stabilityAudioSink{
|
attemptSink := &stabilityAudioSink{
|
||||||
sink: w.sink,
|
sink: w.sink,
|
||||||
onStable: func() {
|
onStable: func() {
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitAudio,
|
Unit: UnitAudio,
|
||||||
State: StatePlaying,
|
State: StatePlaying,
|
||||||
Attempt: attemptNumber,
|
Attempt: attemptNumber,
|
||||||
@@ -134,7 +135,7 @@ func (w *AudioWorker) Run(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitAudio,
|
Unit: UnitAudio,
|
||||||
State: StateReconnecting,
|
State: StateReconnecting,
|
||||||
Attempt: attemptNumber + 1,
|
Attempt: attemptNumber + 1,
|
||||||
@@ -154,11 +155,11 @@ func (w *AudioWorker) Run(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitAudio,
|
Unit: UnitAudio,
|
||||||
State: StateStopping,
|
State: StateStopping,
|
||||||
})
|
})
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitAudio,
|
Unit: UnitAudio,
|
||||||
State: StateIdle,
|
State: StateIdle,
|
||||||
})
|
})
|
||||||
@@ -166,7 +167,7 @@ func (w *AudioWorker) Run(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitAudio,
|
Unit: UnitAudio,
|
||||||
State: StateFailed,
|
State: StateFailed,
|
||||||
Attempt: attemptNumber,
|
Attempt: attemptNumber,
|
||||||
@@ -176,7 +177,7 @@ func (w *AudioWorker) Run(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitAudio,
|
Unit: UnitAudio,
|
||||||
State: StateIdle,
|
State: StateIdle,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -116,6 +116,31 @@ func TestAudioWorkerRejectsInactiveFeed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAudioWorkerStatusesInheritGeneration(t *testing.T) {
|
||||||
|
openErr := errors.New("unavailable")
|
||||||
|
var statuses []Status
|
||||||
|
worker := newAudioWorkerForTest(
|
||||||
|
t,
|
||||||
|
&queuedAudioFactory{errs: []error{openErr}},
|
||||||
|
&fakeAudioSink{},
|
||||||
|
1,
|
||||||
|
func(error) bool { return true },
|
||||||
|
func(status Status) { statuses = append(statuses, status) },
|
||||||
|
)
|
||||||
|
_ = worker.Run(
|
||||||
|
withGeneration(context.Background(), 8),
|
||||||
|
FeedConfig{Domain: "/audio", UUID: "audio", Active: true},
|
||||||
|
)
|
||||||
|
if len(statuses) == 0 {
|
||||||
|
t.Fatal("no statuses emitted")
|
||||||
|
}
|
||||||
|
for _, status := range statuses {
|
||||||
|
if status.Generation != 8 {
|
||||||
|
t.Fatalf("status generation = %d, want 8: %+v", status.Generation, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAudioWorkerPublishesPlayingThenFailed(t *testing.T) {
|
func TestAudioWorkerPublishesPlayingThenFailed(t *testing.T) {
|
||||||
readErr := errors.New("audio disappeared")
|
readErr := errors.New("audio disappeared")
|
||||||
reader := &fakeAudioReader{
|
reader := &fakeAudioReader{
|
||||||
|
|||||||
@@ -109,9 +109,8 @@ func (c *SessionController) Run(
|
|||||||
}
|
}
|
||||||
|
|
||||||
desired := initial
|
desired := initial
|
||||||
runtime := c.startSessionRuntime(ctx, plan)
|
|
||||||
|
|
||||||
generation := uint64(1)
|
generation := uint64(1)
|
||||||
|
runtime := c.startSessionRuntime(ctx, plan, generation)
|
||||||
c.publish(SessionSnapshot{
|
c.publish(SessionSnapshot{
|
||||||
Desired: initial,
|
Desired: initial,
|
||||||
Plan: plan,
|
Plan: plan,
|
||||||
@@ -148,11 +147,16 @@ func (c *SessionController) Run(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nextGeneration := generation
|
||||||
|
if plan.Topology != nextPlan.Topology {
|
||||||
|
nextGeneration++
|
||||||
|
}
|
||||||
nextRuntime, err := c.reconcileSessionRuntime(
|
nextRuntime, err := c.reconcileSessionRuntime(
|
||||||
ctx,
|
ctx,
|
||||||
runtime,
|
runtime,
|
||||||
plan,
|
plan,
|
||||||
nextPlan,
|
nextPlan,
|
||||||
|
nextGeneration,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stopSessionRuntime(runtime)
|
stopSessionRuntime(runtime)
|
||||||
@@ -162,9 +166,7 @@ func (c *SessionController) Run(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if plan.Topology != nextPlan.Topology {
|
generation = nextGeneration
|
||||||
generation++
|
|
||||||
}
|
|
||||||
desired = nextDesired
|
desired = nextDesired
|
||||||
plan = nextPlan
|
plan = nextPlan
|
||||||
runtime = nextRuntime
|
runtime = nextRuntime
|
||||||
@@ -180,12 +182,13 @@ func (c *SessionController) Run(
|
|||||||
func (c *SessionController) startSessionRuntime(
|
func (c *SessionController) startSessionRuntime(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
plan SessionPlan,
|
plan SessionPlan,
|
||||||
|
generation uint64,
|
||||||
) *sessionRuntime {
|
) *sessionRuntime {
|
||||||
if plan.Topology == TopologyIdle {
|
if plan.Topology == TopologyIdle {
|
||||||
return &sessionRuntime{topology: TopologyIdle}
|
return &sessionRuntime{topology: TopologyIdle}
|
||||||
}
|
}
|
||||||
|
|
||||||
runtimeCtx, cancel := context.WithCancel(ctx)
|
runtimeCtx, cancel := context.WithCancel(withGeneration(ctx, generation))
|
||||||
runtime := &sessionRuntime{
|
runtime := &sessionRuntime{
|
||||||
topology: plan.Topology,
|
topology: plan.Topology,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
@@ -255,13 +258,14 @@ func (c *SessionController) reconcileSessionRuntime(
|
|||||||
runtime *sessionRuntime,
|
runtime *sessionRuntime,
|
||||||
current SessionPlan,
|
current SessionPlan,
|
||||||
next SessionPlan,
|
next SessionPlan,
|
||||||
|
nextGeneration uint64,
|
||||||
) (*sessionRuntime, error) {
|
) (*sessionRuntime, error) {
|
||||||
if current.Topology != next.Topology {
|
if current.Topology != next.Topology {
|
||||||
stopSessionRuntime(runtime)
|
stopSessionRuntime(runtime)
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return runtime, err
|
return runtime, err
|
||||||
}
|
}
|
||||||
return c.startSessionRuntime(ctx, next), nil
|
return c.startSessionRuntime(ctx, next, nextGeneration), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch next.Topology {
|
switch next.Topology {
|
||||||
|
|||||||
@@ -88,10 +88,11 @@ func TestNewSessionControllerStoresSyncPredicate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type controllerEvent struct {
|
type controllerEvent struct {
|
||||||
unit Unit
|
unit Unit
|
||||||
action string
|
action string
|
||||||
feed FeedConfig
|
generation uint64
|
||||||
pair SyncPairConfig
|
feed FeedConfig
|
||||||
|
pair SyncPairConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type recordingVideoSlot struct{ events chan<- controllerEvent }
|
type recordingVideoSlot struct{ events chan<- controllerEvent }
|
||||||
@@ -101,7 +102,10 @@ func (s recordingVideoSlot) Run(
|
|||||||
initial FeedConfig,
|
initial FeedConfig,
|
||||||
commands <-chan FeedConfig,
|
commands <-chan FeedConfig,
|
||||||
) error {
|
) error {
|
||||||
s.events <- controllerEvent{unit: UnitVideo, action: "start", feed: initial}
|
s.events <- controllerEvent{
|
||||||
|
unit: UnitVideo, action: "start",
|
||||||
|
generation: generationFromContext(ctx), feed: initial,
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case config := <-commands:
|
case config := <-commands:
|
||||||
@@ -120,7 +124,10 @@ func (s recordingAudioSlot) Run(
|
|||||||
initial FeedConfig,
|
initial FeedConfig,
|
||||||
commands <-chan FeedConfig,
|
commands <-chan FeedConfig,
|
||||||
) error {
|
) error {
|
||||||
s.events <- controllerEvent{unit: UnitAudio, action: "start", feed: initial}
|
s.events <- controllerEvent{
|
||||||
|
unit: UnitAudio, action: "start",
|
||||||
|
generation: generationFromContext(ctx), feed: initial,
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case config := <-commands:
|
case config := <-commands:
|
||||||
@@ -139,7 +146,10 @@ func (s recordingSyncSlot) Run(
|
|||||||
initial SyncPairConfig,
|
initial SyncPairConfig,
|
||||||
commands <-chan SyncPairConfig,
|
commands <-chan SyncPairConfig,
|
||||||
) error {
|
) error {
|
||||||
s.events <- controllerEvent{unit: UnitSync, action: "start", pair: initial}
|
s.events <- controllerEvent{
|
||||||
|
unit: UnitSync, action: "start",
|
||||||
|
generation: generationFromContext(ctx), pair: initial,
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case config := <-commands:
|
case config := <-commands:
|
||||||
@@ -291,6 +301,9 @@ func TestSessionControllerStopsIndependentSlotsBeforeStartingSync(t *testing.T)
|
|||||||
if !stopped[UnitVideo] || !stopped[UnitAudio] {
|
if !stopped[UnitVideo] || !stopped[UnitAudio] {
|
||||||
t.Fatalf("sync started before both independent slots stopped: %v", stopped)
|
t.Fatalf("sync started before both independent slots stopped: %v", stopped)
|
||||||
}
|
}
|
||||||
|
if event.generation != 2 {
|
||||||
|
t.Fatalf("sync runtime generation = %d, want 2", event.generation)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if event.action != "stop" || (event.unit != UnitVideo && event.unit != UnitAudio) {
|
if event.action != "stop" || (event.unit != UnitVideo && event.unit != UnitAudio) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package playback
|
package playback
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -27,6 +28,7 @@ const (
|
|||||||
type Status struct {
|
type Status struct {
|
||||||
Unit Unit
|
Unit Unit
|
||||||
State State
|
State State
|
||||||
|
Generation uint64
|
||||||
Attempt int
|
Attempt int
|
||||||
FailedAttempts int
|
FailedAttempts int
|
||||||
RetryIn time.Duration
|
RetryIn time.Duration
|
||||||
@@ -35,6 +37,17 @@ type Status struct {
|
|||||||
|
|
||||||
type StatusObserver func(Status)
|
type StatusObserver func(Status)
|
||||||
|
|
||||||
|
type generationContextKey struct{}
|
||||||
|
|
||||||
|
func withGeneration(ctx context.Context, generation uint64) context.Context {
|
||||||
|
return context.WithValue(ctx, generationContextKey{}, generation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generationFromContext(ctx context.Context) uint64 {
|
||||||
|
generation, _ := ctx.Value(generationContextKey{}).(uint64)
|
||||||
|
return generation
|
||||||
|
}
|
||||||
|
|
||||||
func (u Unit) String() string {
|
func (u Unit) String() string {
|
||||||
switch u {
|
switch u {
|
||||||
case UnitVideo:
|
case UnitVideo:
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
package playback
|
package playback
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestGenerationContext(t *testing.T) {
|
||||||
|
if got := generationFromContext(context.Background()); got != 0 {
|
||||||
|
t.Fatalf("background generation = %d, want 0", got)
|
||||||
|
}
|
||||||
|
ctx := withGeneration(context.Background(), 42)
|
||||||
|
if got := generationFromContext(ctx); got != 42 {
|
||||||
|
t.Fatalf("generation = %d, want 42", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStatusPreservesValues(t *testing.T) {
|
func TestStatusPreservesValues(t *testing.T) {
|
||||||
wantErr := errors.New("producer missing")
|
wantErr := errors.New("producer missing")
|
||||||
status := Status{
|
status := Status{
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ package playback
|
|||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
type StatusStore struct {
|
type StatusStore struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
statuses map[Unit]Status
|
generation uint64
|
||||||
|
statuses map[Unit]Status
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStatusStore() *StatusStore {
|
func NewStatusStore() *StatusStore {
|
||||||
@@ -15,8 +16,15 @@ func NewStatusStore() *StatusStore {
|
|||||||
|
|
||||||
func (s *StatusStore) Observe(status Status) {
|
func (s *StatusStore) Observe(status Status) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if status.Generation < s.generation {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if status.Generation > s.generation {
|
||||||
|
clear(s.statuses)
|
||||||
|
s.generation = status.Generation
|
||||||
|
}
|
||||||
s.statuses[status.Unit] = status
|
s.statuses[status.Unit] = status
|
||||||
s.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StatusStore) Snapshot(unit Unit) (Status, bool) {
|
func (s *StatusStore) Snapshot(unit Unit) (Status, bool) {
|
||||||
|
|||||||
@@ -106,3 +106,50 @@ func TestStatusStoreConcurrentAccess(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStatusStoreNewGenerationClearsPreviousUnits(t *testing.T) {
|
||||||
|
store := NewStatusStore()
|
||||||
|
store.Observe(Status{Unit: UnitVideo, State: StatePlaying, Generation: 1})
|
||||||
|
store.Observe(Status{Unit: UnitAudio, State: StatePlaying, Generation: 1})
|
||||||
|
want := Status{Unit: UnitSync, State: StateConnecting, Generation: 2}
|
||||||
|
store.Observe(want)
|
||||||
|
|
||||||
|
if _, ok := store.Snapshot(UnitVideo); ok {
|
||||||
|
t.Fatal("video status survived generation change")
|
||||||
|
}
|
||||||
|
if _, ok := store.Snapshot(UnitAudio); ok {
|
||||||
|
t.Fatal("audio status survived generation change")
|
||||||
|
}
|
||||||
|
if got, ok := store.Snapshot(UnitSync); !ok || got != want {
|
||||||
|
t.Fatalf("sync Snapshot() = %#v, %t; want %#v, true", got, ok, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatusStoreIgnoresOlderGeneration(t *testing.T) {
|
||||||
|
store := NewStatusStore()
|
||||||
|
want := Status{Unit: UnitSync, State: StatePlaying, Generation: 3}
|
||||||
|
store.Observe(want)
|
||||||
|
store.Observe(Status{Unit: UnitVideo, State: StateIdle, Generation: 2})
|
||||||
|
|
||||||
|
if _, ok := store.Snapshot(UnitVideo); ok {
|
||||||
|
t.Fatal("older video status was stored")
|
||||||
|
}
|
||||||
|
if got, ok := store.Snapshot(UnitSync); !ok || got != want {
|
||||||
|
t.Fatalf("sync Snapshot() = %#v, %t; want %#v, true", got, ok, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatusStoreKeepsEqualGenerationUnitsIndependent(t *testing.T) {
|
||||||
|
store := NewStatusStore()
|
||||||
|
wantVideo := Status{Unit: UnitVideo, State: StatePlaying, Generation: 4}
|
||||||
|
wantAudio := Status{Unit: UnitAudio, State: StateReconnecting, Generation: 4}
|
||||||
|
store.Observe(wantVideo)
|
||||||
|
store.Observe(wantAudio)
|
||||||
|
|
||||||
|
if got, ok := store.Snapshot(UnitVideo); !ok || got != wantVideo {
|
||||||
|
t.Fatalf("video Snapshot() = %#v, %t", got, ok)
|
||||||
|
}
|
||||||
|
if got, ok := store.Snapshot(UnitAudio); !ok || got != wantAudio {
|
||||||
|
t.Fatalf("audio Snapshot() = %#v, %t", got, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ func NewSyncWorker(
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *SyncWorker) emit(status Status) {
|
func (w *SyncWorker) emit(ctx context.Context, status Status) {
|
||||||
|
status.Generation = generationFromContext(ctx)
|
||||||
if w.observer != nil {
|
if w.observer != nil {
|
||||||
w.observer(status)
|
w.observer(status)
|
||||||
}
|
}
|
||||||
@@ -90,7 +91,7 @@ func (w *SyncWorker) Run(
|
|||||||
if attemptNumber > 1 {
|
if attemptNumber > 1 {
|
||||||
state = StateReconnecting
|
state = StateReconnecting
|
||||||
}
|
}
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitSync,
|
Unit: UnitSync,
|
||||||
State: state,
|
State: state,
|
||||||
Attempt: attemptNumber,
|
Attempt: attemptNumber,
|
||||||
@@ -99,7 +100,7 @@ func (w *SyncWorker) Run(
|
|||||||
attemptAudioSink := &stabilityAudioSink{
|
attemptAudioSink := &stabilityAudioSink{
|
||||||
sink: w.audioSink,
|
sink: w.audioSink,
|
||||||
onStable: func() {
|
onStable: func() {
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitSync,
|
Unit: UnitSync,
|
||||||
State: StatePlaying,
|
State: StatePlaying,
|
||||||
Attempt: attemptNumber,
|
Attempt: attemptNumber,
|
||||||
@@ -133,7 +134,7 @@ func (w *SyncWorker) Run(
|
|||||||
if !event.WillRetry {
|
if !event.WillRetry {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitSync,
|
Unit: UnitSync,
|
||||||
State: StateReconnecting,
|
State: StateReconnecting,
|
||||||
Attempt: attemptNumber + 1,
|
Attempt: attemptNumber + 1,
|
||||||
@@ -151,18 +152,18 @@ func (w *SyncWorker) Run(
|
|||||||
observeRetry,
|
observeRetry,
|
||||||
)
|
)
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitSync,
|
Unit: UnitSync,
|
||||||
State: StateStopping,
|
State: StateStopping,
|
||||||
})
|
})
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitSync,
|
Unit: UnitSync,
|
||||||
State: StateIdle,
|
State: StateIdle,
|
||||||
})
|
})
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitSync,
|
Unit: UnitSync,
|
||||||
State: StateFailed,
|
State: StateFailed,
|
||||||
Attempt: attemptNumber,
|
Attempt: attemptNumber,
|
||||||
@@ -171,7 +172,7 @@ func (w *SyncWorker) Run(
|
|||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitSync,
|
Unit: UnitSync,
|
||||||
State: StateIdle,
|
State: StateIdle,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -126,6 +126,29 @@ func TestSyncWorkerRejectsInvalidOrInactiveFeeds(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSyncWorkerStatusesInheritGeneration(t *testing.T) {
|
||||||
|
openErr := errors.New("unavailable")
|
||||||
|
var statuses []Status
|
||||||
|
worker := newTestSyncWorker(
|
||||||
|
t,
|
||||||
|
&scriptedSyncFactory{results: []syncOpenResult{{err: openErr}}},
|
||||||
|
&fakeVideoSink{},
|
||||||
|
&fakeAudioSink{},
|
||||||
|
1,
|
||||||
|
func(status Status) { statuses = append(statuses, status) },
|
||||||
|
)
|
||||||
|
video, audio := activeSyncConfigs()
|
||||||
|
_ = worker.Run(withGeneration(context.Background(), 9), video, audio)
|
||||||
|
if len(statuses) == 0 {
|
||||||
|
t.Fatal("no statuses emitted")
|
||||||
|
}
|
||||||
|
for _, status := range statuses {
|
||||||
|
if status.Generation != 9 {
|
||||||
|
t.Fatalf("status generation = %d, want 9: %+v", status.Generation, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSyncWorkerExhaustsOpenRetries(t *testing.T) {
|
func TestSyncWorkerExhaustsOpenRetries(t *testing.T) {
|
||||||
openErr := errors.New("sync producer unavailable")
|
openErr := errors.New("sync producer unavailable")
|
||||||
factory := &scriptedSyncFactory{results: []syncOpenResult{{err: openErr}, {err: openErr}}}
|
factory := &scriptedSyncFactory{results: []syncOpenResult{{err: openErr}, {err: openErr}}}
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ func (s *stabilityVideoSink) ConsumeVideo(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *VideoWorker) emit(status Status) {
|
func (w *VideoWorker) emit(ctx context.Context, status Status) {
|
||||||
|
status.Generation = generationFromContext(ctx)
|
||||||
if w.observer != nil {
|
if w.observer != nil {
|
||||||
w.observer(status)
|
w.observer(status)
|
||||||
}
|
}
|
||||||
@@ -99,7 +100,7 @@ func (w *VideoWorker) Run(
|
|||||||
if attemptNumber > 1 {
|
if attemptNumber > 1 {
|
||||||
state = StateReconnecting
|
state = StateReconnecting
|
||||||
}
|
}
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitVideo,
|
Unit: UnitVideo,
|
||||||
State: state,
|
State: state,
|
||||||
Attempt: attemptNumber,
|
Attempt: attemptNumber,
|
||||||
@@ -108,7 +109,7 @@ func (w *VideoWorker) Run(
|
|||||||
attemptSink := &stabilityVideoSink{
|
attemptSink := &stabilityVideoSink{
|
||||||
sink: w.sink,
|
sink: w.sink,
|
||||||
onStable: func() {
|
onStable: func() {
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitVideo,
|
Unit: UnitVideo,
|
||||||
State: StatePlaying,
|
State: StatePlaying,
|
||||||
Attempt: attemptNumber,
|
Attempt: attemptNumber,
|
||||||
@@ -134,7 +135,7 @@ func (w *VideoWorker) Run(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitVideo,
|
Unit: UnitVideo,
|
||||||
State: StateReconnecting,
|
State: StateReconnecting,
|
||||||
Attempt: attemptNumber + 1,
|
Attempt: attemptNumber + 1,
|
||||||
@@ -154,11 +155,11 @@ func (w *VideoWorker) Run(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitVideo,
|
Unit: UnitVideo,
|
||||||
State: StateStopping,
|
State: StateStopping,
|
||||||
})
|
})
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitVideo,
|
Unit: UnitVideo,
|
||||||
State: StateIdle,
|
State: StateIdle,
|
||||||
})
|
})
|
||||||
@@ -166,7 +167,7 @@ func (w *VideoWorker) Run(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitVideo,
|
Unit: UnitVideo,
|
||||||
State: StateFailed,
|
State: StateFailed,
|
||||||
Attempt: attemptNumber,
|
Attempt: attemptNumber,
|
||||||
@@ -176,7 +177,7 @@ func (w *VideoWorker) Run(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
w.emit(Status{
|
w.emit(ctx, Status{
|
||||||
Unit: UnitVideo,
|
Unit: UnitVideo,
|
||||||
State: StateIdle,
|
State: StateIdle,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -151,6 +151,28 @@ func TestVideoWorkerRejectsInactiveFeed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVideoWorkerStatusesInheritGeneration(t *testing.T) {
|
||||||
|
openErr := errors.New("unavailable")
|
||||||
|
var statuses []Status
|
||||||
|
worker := newTestVideoWorker(
|
||||||
|
t,
|
||||||
|
&scriptedVideoFactory{results: []videoOpenResult{{err: openErr}}},
|
||||||
|
&fakeVideoSink{},
|
||||||
|
1,
|
||||||
|
func(error) bool { return true },
|
||||||
|
func(status Status) { statuses = append(statuses, status) },
|
||||||
|
)
|
||||||
|
_ = worker.Run(withGeneration(context.Background(), 7), activeVideoConfig())
|
||||||
|
if len(statuses) == 0 {
|
||||||
|
t.Fatal("no statuses emitted")
|
||||||
|
}
|
||||||
|
for _, status := range statuses {
|
||||||
|
if status.Generation != 7 {
|
||||||
|
t.Fatalf("status generation = %d, want 7: %+v", status.Generation, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestVideoWorkerExhaustsOpenRetries(t *testing.T) {
|
func TestVideoWorkerExhaustsOpenRetries(t *testing.T) {
|
||||||
openErr := errors.New("producer unavailable")
|
openErr := errors.New("producer unavailable")
|
||||||
factory := &scriptedVideoFactory{
|
factory := &scriptedVideoFactory{
|
||||||
|
|||||||
Reference in New Issue
Block a user