Browse Source

fix possible deadlock when communicating with clients

pull/80/head
aler9 5 years ago
parent
commit
980989340b
  1. 791
      client.go
  2. 2
      go.mod
  3. 4
      go.sum
  4. 132
      main.go
  5. 64
      path.go
  6. 41
      source.go

791
client.go

File diff suppressed because it is too large Load Diff

2
go.mod

@ -5,7 +5,7 @@ go 1.12
require ( require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
github.com/aler9/gortsplib v0.0.0-20200829164650-64af75a6b5f6 github.com/aler9/gortsplib v0.0.0-20200831072723-57eae89cc552
github.com/aler9/sdp/v3 v3.0.0-20200719093237-2c3d108a7436 github.com/aler9/sdp/v3 v3.0.0-20200719093237-2c3d108a7436
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/stretchr/testify v1.6.1 github.com/stretchr/testify v1.6.1

4
go.sum

@ -2,8 +2,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/aler9/gortsplib v0.0.0-20200829164650-64af75a6b5f6 h1:orZ3RyemnuPUk8K6Wiwga836KVKF64yQR4/gpA3xXu8= github.com/aler9/gortsplib v0.0.0-20200831072723-57eae89cc552 h1:iSF9Byglyx1yqa32kve+6V49wnfI1PB9ciSUBEUO0b0=
github.com/aler9/gortsplib v0.0.0-20200829164650-64af75a6b5f6/go.mod h1:kBMvjIdOHRjLdV+oT28JD72JUPpJuwxOc9u72GG8GpY= github.com/aler9/gortsplib v0.0.0-20200831072723-57eae89cc552/go.mod h1:kBMvjIdOHRjLdV+oT28JD72JUPpJuwxOc9u72GG8GpY=
github.com/aler9/sdp/v3 v3.0.0-20200719093237-2c3d108a7436 h1:W0iNErWKvSAyJBNVx+qQoyFrWOFVgS6f/WEME/D3EZc= github.com/aler9/sdp/v3 v3.0.0-20200719093237-2c3d108a7436 h1:W0iNErWKvSAyJBNVx+qQoyFrWOFVgS6f/WEME/D3EZc=
github.com/aler9/sdp/v3 v3.0.0-20200719093237-2c3d108a7436/go.mod h1:OnlEK3QI7YtM+ShZWtGajmOHLZ3bjU80AcIS5e34i1U= github.com/aler9/sdp/v3 v3.0.0-20200719093237-2c3d108a7436/go.mod h1:OnlEK3QI7YtM+ShZWtGajmOHLZ3bjU80AcIS5e34i1U=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=

132
main.go

@ -43,7 +43,6 @@ type programEventClientNew struct {
func (programEventClientNew) isProgramEvent() {} func (programEventClientNew) isProgramEvent() {}
type programEventClientClose struct { type programEventClientClose struct {
done chan struct{}
client *client client *client
} }
@ -75,41 +74,18 @@ type programEventClientSetupPlay struct {
func (programEventClientSetupPlay) isProgramEvent() {} func (programEventClientSetupPlay) isProgramEvent() {}
type programEventClientSetupRecord struct {
res chan error
client *client
}
func (programEventClientSetupRecord) isProgramEvent() {}
type programEventClientPlay struct { type programEventClientPlay struct {
done chan struct{}
client *client client *client
} }
func (programEventClientPlay) isProgramEvent() {} func (programEventClientPlay) isProgramEvent() {}
type programEventClientPlayStop struct {
done chan struct{}
client *client
}
func (programEventClientPlayStop) isProgramEvent() {}
type programEventClientRecord struct { type programEventClientRecord struct {
done chan struct{}
client *client client *client
} }
func (programEventClientRecord) isProgramEvent() {} func (programEventClientRecord) isProgramEvent() {}
type programEventClientRecordStop struct {
done chan struct{}
client *client
}
func (programEventClientRecordStop) isProgramEvent() {}
type programEventClientFrameUdp struct { type programEventClientFrameUdp struct {
addr *net.UDPAddr addr *net.UDPAddr
streamType gortsplib.StreamType streamType gortsplib.StreamType
@ -312,14 +288,10 @@ outer:
c.log("connected") c.log("connected")
case programEventClientClose: case programEventClientClose:
delete(p.clients, evt.client) if _, ok := p.clients[evt.client]; !ok {
continue
if evt.client.path != nil && evt.client.path.publisher == evt.client {
evt.client.path.onPublisherRemove()
} }
p.closeClient(evt.client)
evt.client.log("disconnected")
close(evt.done)
case programEventClientDescribe: case programEventClientDescribe:
// create path if not exist // create path if not exist
@ -341,7 +313,12 @@ outer:
} }
} }
p.paths[evt.pathName].onPublisherNew(evt.client, evt.sdpText, evt.sdpParsed) p.paths[evt.pathName].publisher = evt.client
p.paths[evt.pathName].publisherSdpText = evt.sdpText
p.paths[evt.pathName].publisherSdpParsed = evt.sdpParsed
evt.client.path = p.paths[evt.pathName]
evt.client.state = clientStatePreRecord
evt.res <- nil evt.res <- nil
case programEventClientSetupPlay: case programEventClientSetupPlay:
@ -360,19 +337,9 @@ outer:
evt.client.state = clientStatePrePlay evt.client.state = clientStatePrePlay
evt.res <- nil evt.res <- nil
case programEventClientSetupRecord:
evt.client.state = clientStatePreRecord
evt.res <- nil
case programEventClientPlay: case programEventClientPlay:
p.readerCount += 1 p.readerCount += 1
evt.client.state = clientStatePlay evt.client.state = clientStatePlay
close(evt.done)
case programEventClientPlayStop:
p.readerCount -= 1
evt.client.state = clientStatePrePlay
close(evt.done)
case programEventClientRecord: case programEventClientRecord:
p.publisherCount += 1 p.publisherCount += 1
@ -397,22 +364,6 @@ outer:
} }
evt.client.path.onPublisherSetReady() evt.client.path.onPublisherSetReady()
close(evt.done)
case programEventClientRecordStop:
p.publisherCount -= 1
evt.client.state = clientStatePreRecord
if evt.client.streamProtocol == gortsplib.StreamProtocolUdp {
for _, track := range evt.client.streamTracks {
key := makeUdpClientAddr(evt.client.ip(), track.rtpPort)
delete(p.udpClientsByAddr, key)
key = makeUdpClientAddr(evt.client.ip(), track.rtcpPort)
delete(p.udpClientsByAddr, key)
}
}
evt.client.path.onPublisherSetNotReady()
close(evt.done)
case programEventClientFrameUdp: case programEventClientFrameUdp:
pub, ok := p.udpClientsByAddr[makeUdpClientAddr(evt.addr.IP, evt.addr.Port)] pub, ok := p.udpClientsByAddr[makeUdpClientAddr(evt.addr.IP, evt.addr.Port)]
@ -454,32 +405,11 @@ outer:
case programEventMetrics: case programEventMetrics:
evt.res <- nil evt.res <- nil
case programEventClientClose:
close(evt.done)
case programEventClientDescribe:
evt.client.describeRes <- describeRes{nil, fmt.Errorf("terminated")}
case programEventClientAnnounce: case programEventClientAnnounce:
evt.res <- fmt.Errorf("terminated") evt.res <- fmt.Errorf("terminated")
case programEventClientSetupPlay: case programEventClientSetupPlay:
evt.res <- fmt.Errorf("terminated") evt.res <- fmt.Errorf("terminated")
case programEventClientSetupRecord:
evt.res <- fmt.Errorf("terminated")
case programEventClientPlay:
close(evt.done)
case programEventClientPlayStop:
close(evt.done)
case programEventClientRecord:
close(evt.done)
case programEventClientRecordStop:
close(evt.done)
} }
} }
}() }()
@ -499,7 +429,7 @@ outer:
} }
for c := range p.clients { for c := range p.clients {
c.conn.NetConn().Close() p.closeClient(c)
<-c.done <-c.done
} }
@ -536,6 +466,38 @@ func (p *program) findConfForPathName(name string) *confPath {
return nil return nil
} }
func (p *program) closeClient(client *client) {
delete(p.clients, client)
switch client.state {
case clientStatePlay:
p.readerCount -= 1
case clientStateRecord:
p.publisherCount -= 1
if client.streamProtocol == gortsplib.StreamProtocolUdp {
for _, track := range client.streamTracks {
key := makeUdpClientAddr(client.ip(), track.rtpPort)
delete(p.udpClientsByAddr, key)
key = makeUdpClientAddr(client.ip(), track.rtcpPort)
delete(p.udpClientsByAddr, key)
}
}
client.path.onPublisherSetNotReady()
}
if client.path != nil && client.path.publisher == client {
client.path.onPublisherRemove()
}
close(client.terminate)
client.log("disconnected")
}
func (p *program) forwardFrame(path *path, trackId int, streamType gortsplib.StreamType, frame []byte) { func (p *program) forwardFrame(path *path, trackId int, streamType gortsplib.StreamType, frame []byte) {
for c := range p.clients { for c := range p.clients {
if c.path != path || if c.path != path ||
@ -570,12 +532,10 @@ func (p *program) forwardFrame(path *path, trackId int, streamType gortsplib.Str
} }
} else { } else {
c.events <- clientEventFrameTcp{ c.tcpFrame <- &gortsplib.InterleavedFrame{
frame: &gortsplib.InterleavedFrame{ TrackId: trackId,
TrackId: trackId, StreamType: streamType,
StreamType: streamType, Content: frame,
Content: frame,
},
} }
} }
} }

64
path.go

@ -76,7 +76,7 @@ func (pa *path) onInit() {
func (pa *path) onClose() { func (pa *path) onClose() {
if pa.source != nil { if pa.source != nil {
pa.source.events <- sourceEventTerminate{} close(pa.source.terminate)
<-pa.source.done <-pa.source.done
} }
@ -91,6 +91,18 @@ func (pa *path) onClose() {
pa.onDemandCmd.Process.Signal(os.Interrupt) pa.onDemandCmd.Process.Signal(os.Interrupt)
pa.onDemandCmd.Wait() pa.onDemandCmd.Wait()
} }
for c := range pa.p.clients {
if c.path == pa {
if c.state == clientStateWaitDescription {
c.path = nil
c.state = clientStateInitial
c.describe <- describeRes{nil, fmt.Errorf("publisher of path '%s' has timed out", pa.name)}
} else {
pa.p.closeClient(c)
}
}
}
} }
func (pa *path) hasClients() bool { func (pa *path) hasClients() bool {
@ -104,7 +116,7 @@ func (pa *path) hasClients() bool {
func (pa *path) hasClientsWaitingDescribe() bool { func (pa *path) hasClientsWaitingDescribe() bool {
for c := range pa.p.clients { for c := range pa.p.clients {
if c.state == clientStateWaitingDescription && c.path == pa { if c.state == clientStateWaitDescription && c.path == pa {
return true return true
} }
} }
@ -125,11 +137,11 @@ func (pa *path) onCheck() {
if pa.hasClientsWaitingDescribe() && if pa.hasClientsWaitingDescribe() &&
time.Since(pa.lastDescribeActivation) >= describeTimeout { time.Since(pa.lastDescribeActivation) >= describeTimeout {
for c := range pa.p.clients { for c := range pa.p.clients {
if c.state == clientStateWaitingDescription && if c.state == clientStateWaitDescription &&
c.path == pa { c.path == pa {
c.path = nil c.path = nil
c.state = clientStateInitial c.state = clientStateInitial
c.describeRes <- describeRes{nil, fmt.Errorf("publisher of path '%s' has timed out", pa.name)} c.describe <- describeRes{nil, fmt.Errorf("publisher of path '%s' has timed out", pa.name)}
} }
} }
} }
@ -142,7 +154,7 @@ func (pa *path) onCheck() {
time.Since(pa.lastDescribeReq) >= sourceStopAfterDescribeSecs { time.Since(pa.lastDescribeReq) >= sourceStopAfterDescribeSecs {
pa.source.log("stopping since we're not requested anymore") pa.source.log("stopping since we're not requested anymore")
pa.source.state = sourceStateStopped pa.source.state = sourceStateStopped
pa.source.events <- sourceEventApplyState{pa.source.state} pa.source.setState <- pa.source.state
} }
// stop on demand command if needed // stop on demand command if needed
@ -164,17 +176,17 @@ func (pa *path) onCheck() {
} }
} }
func (pa *path) onPublisherNew(client *client, sdpText []byte, sdpParsed *sdp.SessionDescription) {
pa.publisher = client
pa.publisherSdpText = sdpText
pa.publisherSdpParsed = sdpParsed
client.path = pa
client.state = clientStateAnnounce
}
func (pa *path) onPublisherRemove() { func (pa *path) onPublisherRemove() {
pa.publisher = nil pa.publisher = nil
// close all clients that are reading or waiting for reading
for c := range pa.p.clients {
if c.path == pa &&
c.state != clientStateWaitDescription &&
c != pa.publisher {
pa.p.closeClient(c)
}
}
} }
func (pa *path) onPublisherSetReady() { func (pa *path) onPublisherSetReady() {
@ -182,11 +194,11 @@ func (pa *path) onPublisherSetReady() {
// reply to all clients that are waiting for a description // reply to all clients that are waiting for a description
for c := range pa.p.clients { for c := range pa.p.clients {
if c.state == clientStateWaitingDescription && if c.state == clientStateWaitDescription &&
c.path == pa { c.path == pa {
c.path = nil c.path = nil
c.state = clientStateInitial c.state = clientStateInitial
c.describeRes <- describeRes{pa.publisherSdpText, nil} c.describe <- describeRes{pa.publisherSdpText, nil}
} }
} }
} }
@ -194,12 +206,12 @@ func (pa *path) onPublisherSetReady() {
func (pa *path) onPublisherSetNotReady() { func (pa *path) onPublisherSetNotReady() {
pa.publisherReady = false pa.publisherReady = false
// close all clients that are reading // close all clients that are reading or waiting for reading
for c := range pa.p.clients { for c := range pa.p.clients {
if c.state != clientStateWaitingDescription && if c.path == pa &&
c != pa.publisher && c.state != clientStateWaitDescription &&
c.path == pa { c != pa.publisher {
c.conn.NetConn().Close() pa.p.closeClient(c)
} }
} }
} }
@ -228,11 +240,11 @@ func (pa *path) onDescribe(client *client) {
} }
client.path = pa client.path = pa
client.state = clientStateWaitingDescription client.state = clientStateWaitDescription
// no on-demand: reply with 404 // no on-demand: reply with 404
} else { } else {
client.describeRes <- describeRes{nil, fmt.Errorf("no one is publishing on path '%s'", pa.name)} client.describe <- describeRes{nil, fmt.Errorf("no one is publishing on path '%s'", pa.name)}
} }
// publisher was found but is not ready: put the client on hold // publisher was found but is not ready: put the client on hold
@ -241,14 +253,14 @@ func (pa *path) onDescribe(client *client) {
pa.source.log("starting on demand") pa.source.log("starting on demand")
pa.lastDescribeActivation = time.Now() pa.lastDescribeActivation = time.Now()
pa.source.state = sourceStateRunning pa.source.state = sourceStateRunning
pa.source.events <- sourceEventApplyState{pa.source.state} pa.source.setState <- pa.source.state
} }
client.path = pa client.path = pa
client.state = clientStateWaitingDescription client.state = clientStateWaitDescription
// publisher was found and is ready // publisher was found and is ready
} else { } else {
client.describeRes <- describeRes{pa.publisherSdpText, nil} client.describe <- describeRes{pa.publisherSdpText, nil}
} }
} }

41
source.go

@ -23,20 +23,6 @@ const (
sourceStateRunning sourceStateRunning
) )
type sourceEvent interface {
isSourceEvent()
}
type sourceEventApplyState struct {
state sourceState
}
func (sourceEventApplyState) isSourceEvent() {}
type sourceEventTerminate struct{}
func (sourceEventTerminate) isSourceEvent() {}
type source struct { type source struct {
p *program p *program
path *path path *path
@ -44,17 +30,19 @@ type source struct {
state sourceState state sourceState
tracks []*gortsplib.Track tracks []*gortsplib.Track
events chan sourceEvent setState chan sourceState
done chan struct{} terminate chan struct{}
done chan struct{}
} }
func newSource(p *program, path *path, confp *confPath) *source { func newSource(p *program, path *path, confp *confPath) *source {
s := &source{ s := &source{
p: p, p: p,
path: path, path: path,
confp: confp, confp: confp,
events: make(chan sourceEvent), setState: make(chan sourceState),
done: make(chan struct{}), terminate: make(chan struct{}),
done: make(chan struct{}),
} }
if confp.SourceOnDemand { if confp.SourceOnDemand {
@ -99,12 +87,12 @@ func (s *source) run() {
applyState(s.state) applyState(s.state)
outer: outer:
for rawEvt := range s.events { for {
switch evt := rawEvt.(type) { select {
case sourceEventApplyState: case state := <-s.setState:
applyState(evt.state) applyState(state)
case sourceEventTerminate: case <-s.terminate:
break outer break outer
} }
} }
@ -114,6 +102,7 @@ outer:
<-doDone <-doDone
} }
close(s.setState)
close(s.done) close(s.done)
} }

Loading…
Cancel
Save