playlist retry-exhaustion handling

This commit is contained in:
Dmitry Sergeev
2026-09-01 23:35:41 +03:00
parent abdfc8e2de
commit 179768ca4a
9 changed files with 346 additions and 45 deletions
+60 -8
View File
@@ -89,7 +89,8 @@ func (c *PlaylistReadinessCoordinator) Run(ctx context.Context) error {
ticker := c.newTicker(c.interval)
defer ticker.Stop()
var emittedRevision uint64
var emittedReadyRevision uint64
var emittedFailedRevision uint64
for {
select {
case <-ctx.Done():
@@ -99,11 +100,7 @@ func (c *PlaylistReadinessCoordinator) Run(ctx context.Context) error {
playlistSnapshot, ok := c.playlist.Snapshot()
if !ok ||
!playlistSnapshot.State.HasSelection ||
playlistSnapshot.Revision == 0 ||
playlistSnapshot.Entry.Duration <= 0 ||
playlistSnapshot.Timing.Started ||
playlistSnapshot.Timing.Paused ||
playlistSnapshot.Revision == emittedRevision {
playlistSnapshot.Revision == 0 {
continue
}
@@ -114,7 +111,30 @@ func (c *PlaylistReadinessCoordinator) Run(ctx context.Context) error {
) {
continue
}
if !IsSessionPlaying(sessionSnapshot, c.statuses.SnapshotAll()) {
statuses := c.statuses.SnapshotAll()
if IsSessionFailed(sessionSnapshot, statuses) {
if playlistSnapshot.Revision == emittedFailedRevision {
continue
}
failed := PlaylistEvent{
Revision: playlistSnapshot.Revision,
Kind: PlaylistEventFailed,
}
select {
case <-ctx.Done():
return ctx.Err()
case c.output <- failed:
emittedFailedRevision = playlistSnapshot.Revision
}
continue
}
if playlistSnapshot.Entry.Duration <= 0 ||
playlistSnapshot.Timing.Started ||
playlistSnapshot.Timing.Paused ||
playlistSnapshot.Revision == emittedReadyRevision {
continue
}
if !IsSessionPlaying(sessionSnapshot, statuses) {
continue
}
@@ -123,7 +143,7 @@ func (c *PlaylistReadinessCoordinator) Run(ctx context.Context) error {
case <-ctx.Done():
return ctx.Err()
case c.output <- ready:
emittedRevision = playlistSnapshot.Revision
emittedReadyRevision = playlistSnapshot.Revision
}
}
}
@@ -193,6 +213,38 @@ func IsSessionPlaying(
}
}
func IsSessionFailed(
session SessionSnapshot,
statuses PlaybackStatusSnapshot,
) bool {
if statuses.Generation != session.Generation {
return false
}
switch session.Plan.Topology {
case TopologyIndependent:
return (session.Plan.Video.Active && statusIsFailed(
statuses.Video, statuses.HasVideo, session.Generation, session.Plan.Video,
)) || (session.Plan.Audio.Active && statusIsFailed(
statuses.Audio, statuses.HasAudio, session.Generation, session.Plan.Audio,
))
case TopologySynchronized:
return statuses.HasSync &&
statuses.Sync.Generation == session.Generation &&
statuses.Sync.State == StateFailed &&
sameSyncSource(statuses.Sync.Pair, session.Plan.Sync)
default:
return false
}
}
func statusIsFailed(status Status, present bool, generation uint64, feed FeedConfig) bool {
return present &&
status.Generation == generation &&
status.State == StateFailed &&
sameFeedSource(status.Feed, feed)
}
func statusIsPlaying(
status Status,
present bool,