Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Properly close microphone before malgo chunk channel #436

Merged
merged 4 commits into from
Aug 9, 2022

Conversation

edaniels
Copy link
Member

@edaniels edaniels commented Aug 8, 2022

Prior to this, there's a chance you would close the chunk channel while malgo is trying to send on it. Instead, it's better to stop the malgo device and then close. That also means we need to discard chunks when trying to stop due to the buffered channel.

@edaniels edaniels requested a review from lherman-cs August 8, 2022 19:53
Comment on lines 152 to 160
select {
case <-closeDone:
return
default:
}
select {
case <-m.chunkChan:
case <-closeDone:
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is <-m.chunkChan always received after <-closeDone?
I guess m.chunkChan and closeDone may be triggered almost at the same time and may return before consuming m.chunkChan at low chance.

func (m *microphone) Close() error {
	if m.deviceCloseFunc != nil {
		m.deviceCloseFunc()
	}
	// Don't close m.chunkChan to avoid panic due to `send to closed chan`
	// Go GC collects unclosed chan with buffered value when unused:
	//   https://groups.google.com/g/golang-nuts/c/KtIyc5lTRJY/m/6PK6aK5QKHIJ
	return nil
}
...
func (m *microphone) AudioRecord(inputProp prop.Media) (audio.Reader, error) {
	...
	ctx, cancel := context.WithCancel(context.Background())
	onRecvChunk := func(_, chunk []byte, framecount uint32) {
		select {
		case m.chunkChan <- chunk:
		case <-ctx.Done():
		}
	}
	...
	m.deviceCloseFunc = func() {
		closeDeviceOnce.Do(func() {
			cancel() // Unblock onRecvChunk
			device.Stop()
			device.Uninit()
		})
	}

would be more safe?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, good catch. Will use your version. The whole point is to unblock anyway, not consume.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also check your gophers slack!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll still use your way but the original way is also safe because closeDone only happens after we stop the device which means the callback will never fire and no one will get stuck.

@edaniels edaniels requested review from at-wat and removed request for lherman-cs August 8, 2022 23:16
@edaniels
Copy link
Member Author

edaniels commented Aug 8, 2022

@at-wat updated. I left the intentional close in to not rely on an GC implementation detail

@codecov
Copy link

codecov bot commented Aug 8, 2022

Codecov Report

Merging #436 (dcaf53c) into master (5215057) will not change coverage.
The diff coverage is n/a.

@@           Coverage Diff           @@
##           master     #436   +/-   ##
=======================================
  Coverage   47.32%   47.32%           
=======================================
  Files          67       67           
  Lines        4463     4463           
=======================================
  Hits         2112     2112           
  Misses       2226     2226           
  Partials      125      125           

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@@ -87,6 +90,9 @@ func (m *microphone) Open() error {
}

func (m *microphone) Close() error {
if m.deviceCloseFunc != nil {
m.deviceCloseFunc()
}
if m.chunkChan != nil {
close(m.chunkChan)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be called at exactly same timing with m.chunkChan <- chunk and cause panic by low chance.
(same as #414 (comment))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it can be. The close function calls Stop/Uninit (turns out only Uninit is required). Both of those functions wait for the worker thread to return and ultimately will no longer call the stop/data callbacks. Therefore there are no more sends on the channel after and we can safely close it. Additionally you can see the Remarks section in https://miniaudio.docsforge.com/master/api/#ma_device_callback_proc referring to deadlocks and https://miniaudio.docsforge.com/master/api/ma_device_uninit/ showing the worker thread being waited on to terminate.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see, as long as the uninit waits for ongoing callback return, it should be safe.

Maybe moving close(m.chunkChan) into deviceCloseFunc would be better at the readability about this safety logic.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved!

Comment on lines +153 to +156
if m.chunkChan != nil {
close(m.chunkChan)
m.chunkChan = nil
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it's in Once,

Suggested change
if m.chunkChan != nil {
close(m.chunkChan)
m.chunkChan = nil
}
close(m.chunkChan)
m.chunkChan = nil

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left that there because we don't check if we've called Open, so the channel may be nil, hypothetically

Copy link
Member

@at-wat at-wat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@edaniels edaniels merged commit 6f204fa into pion:master Aug 9, 2022
@edaniels edaniels deleted the microphone-close-fix branch August 9, 2022 13:17
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants