@@ -91,9 +91,8 @@ func (ans *webrtcSignalingAnswerer) Start() {
91
91
ans .startStopMu .Lock ()
92
92
defer ans .startStopMu .Unlock ()
93
93
94
- ans .bgWorkersMu . RLock ()
94
+ // No lock is necessary here. It is illegal to call ` ans.Stop` before `ans.Start` returns.
95
95
ans .bgWorkers .Add (1 )
96
- ans .bgWorkersMu .RUnlock ()
97
96
98
97
// attempt to make connection in a loop
99
98
utils .ManagedGo (func () {
@@ -158,17 +157,23 @@ func (ans *webrtcSignalingAnswerer) startAnswerer() {
158
157
}
159
158
return answerClient , nil
160
159
}
161
- ans .bgWorkersMu .RLock ()
162
- ans .bgWorkers .Add (1 )
163
- ans .bgWorkersMu .RUnlock ()
164
160
165
- // Check if closeCtx has errored: underlying answerer may have been
166
- // `Stop`ped, in which case we mark this answer worker as `Done` and
167
- // return.
168
- if err := ans .closeCtx .Err (); err != nil {
169
- ans .bgWorkers .Done ()
161
+ // The answerer may be stopped (canceling the context and waiting on background workers)
162
+ // concurrently to executing the below code. In that circumstance we must guarantee either:
163
+ // * `Stop` waiting on the `bgWorkers` WaitGroup observes our `bgWorkers.Add` or
164
+ // * Our code observes `Stop`s closing of the `closeCtx`
165
+ //
166
+ // We use a mutex to make the read of the `closeCtx` and write to the `bgWorkers` atomic. `Stop`
167
+ // takes a competing mutex around canceling the `closeCtx`.
168
+ ans .bgWorkersMu .RLock ()
169
+ select {
170
+ case <- ans .closeCtx .Done ():
171
+ ans .bgWorkersMu .RUnlock ()
170
172
return
173
+ default :
171
174
}
175
+ ans .bgWorkers .Add (1 )
176
+ ans .bgWorkersMu .RUnlock ()
172
177
173
178
utils .ManagedGo (func () {
174
179
var client webrtcpb.SignalingService_AnswerClient
@@ -186,6 +191,7 @@ func (ans *webrtcSignalingAnswerer) startAnswerer() {
186
191
return
187
192
default :
188
193
}
194
+
189
195
var err error
190
196
// `newAnswer` opens a bidi grpc stream to the signaling server. But otherwise sends no requests.
191
197
client , err = newAnswer ()
@@ -232,10 +238,14 @@ func (ans *webrtcSignalingAnswerer) Stop() {
232
238
ans .startStopMu .Lock ()
233
239
defer ans .startStopMu .Unlock ()
234
240
235
- ans .cancelBgWorkers ()
241
+ // Code adding workers must atomically check the `closeCtx` before adding to the `bgWorkers`
242
+ // wait group. Canceling the context must not split those two operations. We ensure this
243
+ // atomicity by acquiring the `bgWorkersMu` write lock.
236
244
ans .bgWorkersMu .Lock ()
237
- ans .bgWorkers .Wait ()
245
+ ans .cancelBgWorkers ()
246
+ // Background workers require the `bgWorkersMu`. Release the mutex before calling `Wait`.
238
247
ans .bgWorkersMu .Unlock ()
248
+ ans .bgWorkers .Wait ()
239
249
240
250
ans .connMu .Lock ()
241
251
defer ans .connMu .Unlock ()
@@ -383,19 +393,26 @@ func (ans *webrtcSignalingAnswerer) answer(client webrtcpb.SignalingService_Answ
383
393
})
384
394
}
385
395
}
386
- // must spin off to unblock the ICE gatherer
387
- ans .bgWorkersMu .RLock ()
388
- ans .bgWorkers .Add (1 )
389
- ans .bgWorkersMu .RUnlock ()
390
396
391
- // Check if closeCtx has errored: underlying answerer may have been
392
- // `Stop`ped, in which case we mark this answer worker as `Done` and
393
- // return.
394
- if err := ans .closeCtx .Err (); err != nil {
395
- ans .bgWorkers .Done ()
397
+ // The answerer may be stopped (canceling the context and waiting on background workers)
398
+ // concurrently to executing the below code. In that circumstance we must guarantee
399
+ // either:
400
+ // * `Stop` waiting on the `bgWorkers` WaitGroup observes our `bgWorkers.Add` or
401
+ // * Our code observes `Stop`s closing of the `closeCtx`
402
+ //
403
+ // We use a mutex to make the read of the `closeCtx` and write to the `bgWorkers`
404
+ // atomic. `Stop` takes a competing mutex around canceling the `closeCtx`.
405
+ ans .bgWorkersMu .RLock ()
406
+ select {
407
+ case <- ans .closeCtx .Done ():
408
+ ans .bgWorkersMu .RUnlock ()
396
409
return
410
+ default :
397
411
}
412
+ ans .bgWorkers .Add (1 )
413
+ ans .bgWorkersMu .RUnlock ()
398
414
415
+ // must spin off to unblock the ICE gatherer
399
416
utils .PanicCapturingGo (func () {
400
417
defer ans .bgWorkers .Done ()
401
418
0 commit comments