diff --git a/internal/app/media/ready.go b/internal/app/media/ready.go index 0a0dbc4..26e3d19 100644 --- a/internal/app/media/ready.go +++ b/internal/app/media/ready.go @@ -51,7 +51,7 @@ func NewSnapshots(snapshots port.RoomSnapshots, store port.MediaStore, logger *s return &Snapshots{RoomSnapshots: snapshots, store: store, logger: logger} } -// 对象存储出错时,其余条目按曲目表恢复:对象存储不可用时房间仍要能被接管。任期已经加 1,所以这里不再返回错误。 +// 检查出错的条目按曲目表恢复:对象存储不可用时房间仍要能被接管。任期已经加 1,所以这里不再返回错误。 func (s *Snapshots) TakeRoom(ctx context.Context, id string) (uint32, room.Snapshot, error) { term, snap, err := s.RoomSnapshots.TakeRoom(ctx, id) if err != nil { @@ -66,18 +66,22 @@ func (s *Snapshots) TakeRoom(ctx context.Context, id string) (uint32, room.Snaps for i := range snap.Waiting { entries = append(entries, &snap.Waiting[i]) } + var errs []error for _, e := range entries { if !e.Ready || !Stored(e.Track.Source) { continue } exists, err := s.store.Exists(ctx, AudioObject(Key(e.Track.ID))) if err != nil { - s.logger.WarnContext(ctx, "检查曲目的音频失败,其余条目按曲目表恢复", "source", e.Track.Source, "track_id", e.Track.ID, "error", err) - break + errs = append(errs, err) + continue } if !exists { e.Ready, e.Prep = false, track.Prepared{} } } + if len(errs) > 0 { + s.logger.WarnContext(ctx, "检查曲目的音频失败,这些条目按曲目表恢复", "failed", len(errs), "error", errs[0]) + } return term, snap, nil } diff --git a/internal/app/media/ready_test.go b/internal/app/media/ready_test.go index 17ba4cc..d712b54 100644 --- a/internal/app/media/ready_test.go +++ b/internal/app/media/ready_test.go @@ -129,8 +129,34 @@ func TestSnapshotsKeepEntriesWhenStorageFails(t *testing.T) { if !snap.Pending.Ready || !snap.Waiting[0].Ready { t.Error("对象存储出错时,条目应按曲目表恢复") } - if n := strings.Count(logs.String(), "检查曲目的音频失败"); n != 1 { - t.Errorf("日志中记录了 %d 次失败,期望出错后不再检查其余条目:\n%s", n, logs.String()) + if n := strings.Count(logs.String(), "检查曲目的音频失败"); n != 1 || !strings.Contains(logs.String(), `"failed":3`) { + t.Errorf("日志中记录了 %d 次失败,期望只记一次,并写明 3 个条目没有检查到:\n%s", n, logs.String()) + } +} + +type failingObject struct { + port.MediaStore + object string +} + +func (s failingObject) Exists(ctx context.Context, object string) (bool, error) { + if object == s.object { + return false, errors.New("连接被重置") + } + return s.MediaStore.Exists(ctx, object) +} + +func TestSnapshotsCheckRemainingEntriesAfterFailure(t *testing.T) { + store := failingObject{porttest.NewMediaStore(), AudioObject(Key(link.ID))} + _, snap, err := NewSnapshots(snapshots{snap: restoredSnapshot()}, store, slog.New(slog.DiscardHandler)).TakeRoom(t.Context(), "room") + if err != nil { + t.Fatal(err) + } + if !snap.Pending.Ready { + t.Error("检查出错的条目应按曲目表恢复") + } + if snap.Waiting[0].Ready { + t.Error("某个条目检查出错之后,音频已丢失的条目仍应改回准备中") } }