fix(hls): snapshot state in Store and publish fully initialized runs

Address the completion review findings on top of the upstream review
fixes:

- Store runs on the engine's checkpoint goroutine without the task
  lock and serializes the returned value afterwards: take the fetcher
  mutex and return a deep copy instead of the live state pointer,
  matching the HTTP protocol's Store pattern
- Load the journal and per-run base counters before publishing the run
  as active, so Stats/Progress never observe half-initialized fields
- Bind the journal fingerprint to the key IV as well: a rotated IV on
  an otherwise identical plan would silently decrypt to garbage
- Keep every EXT-X-MEDIA member per group instead of only the first
  one, so a group with a URI on a later member is still detected
- After a merge that raced with a pause, keep the staged segments and
  exit silently instead of cleaning them up; drop a dead context
  check and an unused helper, and make the MAP identity and merge
  part-file names unambiguous
This commit is contained in:
sun committed 2026-09-15 16:31:35 +08:00
1 parent f1f17948c6
commit f9410bbcc7
5 files changed
+79 -54

No files matched your search

+17 -3
View File
@@ -59,12 +59,26 @@ func (fm *FetcherManager) DefaultConfig() any {
func (fm *FetcherManager) Store(f fetcher.Fetcher) (data any, err error) {
_f := f.(*Fetcher)
_f.mu.Lock()
defer _f.mu.Unlock()
if _f.state == nil {
return nil, nil
}
// The segment plan is immutable after resolve; the completed-segment
// journal lives on disk next to the segments.
return _f.state, nil
// The engine's periodic checkpoint calls Store without holding the task
// lock and serializes the returned value afterwards, while Resolve/Patch/
// Start mutate the live state under the fetcher mutex: return a snapshot
// copy taken under that mutex, mirroring the HTTP protocol's Store.
return cloneState(_f.state), nil
}
func cloneState(state *fetcherState) *fetcherState {
cloned := *state
cloned.Segments = make([]*Segment, len(state.Segments))
for i, seg := range state.Segments {
s := *seg
cloned.Segments[i] = &s
}
return &cloned
}
func (fm *FetcherManager) Restore() (v any, f func(meta *fetcher.FetcherMeta, v any) fetcher.Fetcher) {