hls: improve muxer performance (#5660)

use a mutex instead of a channel to get current instance.
This commit is contained in:
Alessandro Ros
2026-04-21 20:16:36 +02:00
committed by GitHub
parent 9e077744fd
commit cae9920f00
+38 -34
View File
@@ -44,10 +44,6 @@ type muxerGetInstanceRes struct {
cumulatedOutboundFramesDiscarded uint64 cumulatedOutboundFramesDiscarded uint64
} }
type muxerGetInstanceReq struct {
res chan muxerGetInstanceRes
}
type muxer struct { type muxer struct {
parentCtx context.Context parentCtx context.Context
remoteAddr string remoteAddr string
@@ -71,8 +67,9 @@ type muxer struct {
lastRequestTime atomic.Int64 lastRequestTime atomic.Int64
bytesSent atomic.Uint64 bytesSent atomic.Uint64
// in instanceMutex sync.RWMutex
chGetInstance chan muxerGetInstanceReq instance *muxerInstance
cumulatedOutboundFramesDiscarded uint64
} }
func (m *muxer) initialize() { func (m *muxer) initialize() {
@@ -83,7 +80,6 @@ func (m *muxer) initialize() {
m.created = time.Now() m.created = time.Now()
m.lastRequestTime.Store(time.Now().UnixNano()) m.lastRequestTime.Store(time.Now().UnixNano())
m.bytesSent.Store(0) m.bytesSent.Store(0)
m.chGetInstance = make(chan muxerGetInstanceReq)
m.Log(logger.Info, "created %s", func() string { m.Log(logger.Info, "created %s", func() string {
if m.remoteAddr == "" { if m.remoteAddr == "" {
@@ -92,6 +88,9 @@ func (m *muxer) initialize() {
return "(requested by " + m.remoteAddr + ")" return "(requested by " + m.remoteAddr + ")"
}()) }())
// block first request to getInstance() until the first instance is available
m.instanceMutex.Lock()
m.wg.Add(1) m.wg.Add(1)
go m.run() go m.run()
} }
@@ -132,6 +131,7 @@ func (m *muxer) runInner() error {
}, },
}) })
if err != nil { if err != nil {
m.instanceMutex.Unlock()
return err return err
} }
@@ -139,27 +139,30 @@ func (m *muxer) runInner() error {
defer m.path.RemoveReader(defs.PathRemoveReaderReq{Author: m}) defer m.path.RemoveReader(defs.PathRemoveReaderReq{Author: m})
mi, err := m.createInstance(res.Stream) tmp, err := m.createInstance(res.Stream)
if err != nil { if err != nil {
if m.remoteAddr != "" || errors.Is(err, hls.ErrNoSupportedCodecs) { if m.remoteAddr != "" || errors.Is(err, hls.ErrNoSupportedCodecs) {
m.instanceMutex.Unlock()
return err return err
} }
m.Log(logger.Error, err.Error()) m.Log(logger.Error, err.Error())
mi = nil
} }
m.instance = tmp
m.instanceMutex.Unlock()
defer func() { defer func() {
if mi != nil { if m.instance != nil {
mi.close() m.closeInstance()
} }
}() }()
var instanceError chan error var instanceError chan error
var recreateTimer *time.Timer var recreateTimer *time.Timer
if mi != nil { if m.instance != nil {
instanceError = mi.errorChan() instanceError = m.instance.errorChan()
recreateTimer = emptyTimer() recreateTimer = emptyTimer()
} else { } else {
instanceError = make(chan error) instanceError = make(chan error)
@@ -173,36 +176,29 @@ func (m *muxer) runInner() error {
activityCheckTimer = emptyTimer() activityCheckTimer = emptyTimer()
} }
cumulatedOutboundFramesDiscarded := uint64(0)
for { for {
select { select {
case req := <-m.chGetInstance:
req.res <- muxerGetInstanceRes{
instance: mi,
cumulatedOutboundFramesDiscarded: cumulatedOutboundFramesDiscarded,
}
case err = <-instanceError: case err = <-instanceError:
if m.remoteAddr != "" { if m.remoteAddr != "" {
return err return err
} }
m.Log(logger.Error, err.Error()) m.Log(logger.Error, err.Error())
mi.close() m.closeInstance()
cumulatedOutboundFramesDiscarded += mi.reader.OutboundFramesDiscarded()
mi = nil
instanceError = make(chan error) instanceError = make(chan error)
recreateTimer = time.NewTimer(recreatePause) recreateTimer = time.NewTimer(recreatePause)
case <-recreateTimer.C: case <-recreateTimer.C:
mi, err = m.createInstance(res.Stream) tmp, err = m.createInstance(res.Stream)
if err != nil { if err != nil {
m.Log(logger.Error, err.Error()) m.Log(logger.Error, err.Error())
mi = nil
recreateTimer = time.NewTimer(recreatePause) recreateTimer = time.NewTimer(recreatePause)
} else { } else {
instanceError = mi.errorChan() m.instanceMutex.Lock()
m.instance = tmp
m.instanceMutex.Unlock()
instanceError = m.instance.errorChan()
} }
case <-activityCheckTimer.C: case <-activityCheckTimer.C:
@@ -218,6 +214,16 @@ func (m *muxer) runInner() error {
} }
} }
func (m *muxer) closeInstance() {
m.instanceMutex.Lock()
m.cumulatedOutboundFramesDiscarded += m.instance.reader.OutboundFramesDiscarded()
var tmp *muxerInstance
tmp, m.instance = m.instance, nil
m.instanceMutex.Unlock()
tmp.close()
}
func (m *muxer) createInstance(strm *stream.Stream) (*muxerInstance, error) { func (m *muxer) createInstance(strm *stream.Stream) (*muxerInstance, error) {
mi := &muxerInstance{ mi := &muxerInstance{
variant: m.variant, variant: m.variant,
@@ -235,14 +241,12 @@ func (m *muxer) createInstance(strm *stream.Stream) (*muxerInstance, error) {
} }
func (m *muxer) getInstance() muxerGetInstanceRes { func (m *muxer) getInstance() muxerGetInstanceRes {
req := muxerGetInstanceReq{res: make(chan muxerGetInstanceRes)} m.instanceMutex.RLock()
defer m.instanceMutex.RUnlock()
select { return muxerGetInstanceRes{
case m.chGetInstance <- req: instance: m.instance,
return <-req.res cumulatedOutboundFramesDiscarded: m.cumulatedOutboundFramesDiscarded,
case <-m.ctx.Done():
return muxerGetInstanceRes{}
} }
} }