Description
StopContinuousRecognitionAsync() (and similarly StartContinuousRecognitionAsync()) use an unbuffered channel internally, while the C-layer wait uses math.MaxUint32 timeout (~49 days).
In real-world usage, this combination can easily leak goroutines.
Root Cause
Looking at speech_recognizer.go:
func (recognizer SpeechRecognizer) StopContinuousRecognitionAsync() chan error {
outcome := make(chan error) // unbuffered
go func() {
// ... C calls ...
ret = uintptr(C.recognizer_stop_continuous_recognition_async_wait_for(
recognizer.handleAsyncStopContinuous, math.MaxUint32)) // ~49 days timeout
// ...
outcome <- nil // blocks forever if no reader
}()
return outcome
}
The internal goroutine blocks on outcome <- nil (or outcome <- err) indefinitely if the caller does not read from the returned channel.
Because the C-layer wait uses math.MaxUint32, caller-side timeout patterns (which are reasonable and common) often abandon the channel, making the goroutine leak permanent.
How to Reproduce
// Common pattern: caller adds timeout to avoid waiting forever
stopChan := recognizer.StopContinuousRecognitionAsync()
select {
case err := <-stopChan:
// OK path: goroutine exits
_ = err
case <-time.After(5 * time.Second):
// Timeout path: goroutine leaked
// SDK goroutine is stuck in C.recognizer_stop_continuous_recognition_async_wait_for
// and when it eventually returns, it may block forever on outcome <- nil
}
This issue is also noted in #129.
Suggested Fix
Use a buffered result channel with size 1 so internal goroutines never block on send:
outcome := make(chan error, 1)
This is the standard Go async-result pattern and should be backward-compatible.
Affected methods likely include:
StartContinuousRecognitionAsync
StopContinuousRecognitionAsync
StartKeywordRecognitionAsync
StopKeywordRecognitionAsync
RecognizeOnceAsync
Environment
- Go SDK version: latest (
master)
- OS: Linux
- Go version: 1.22+
Additional Context
When using PushAudioInputStream with continuous recognition, if audioConfig.Close() is called (for example via defer) before StopContinuousRecognitionAsync() completes, the C-layer stop operation may hang indefinitely, making leaks even more likely.
It would help to clarify cleanup order in docs, for example:
- Stop recognition
- Close recognizer
- Close stream
- Close audioConfig
- Close speechConfig
Description
StopContinuousRecognitionAsync()(and similarlyStartContinuousRecognitionAsync()) use an unbuffered channel internally, while the C-layer wait usesmath.MaxUint32timeout (~49 days).In real-world usage, this combination can easily leak goroutines.
Root Cause
Looking at
speech_recognizer.go:The internal goroutine blocks on
outcome <- nil(oroutcome <- err) indefinitely if the caller does not read from the returned channel.Because the C-layer wait uses
math.MaxUint32, caller-side timeout patterns (which are reasonable and common) often abandon the channel, making the goroutine leak permanent.How to Reproduce
This issue is also noted in #129.
Suggested Fix
Use a buffered result channel with size 1 so internal goroutines never block on send:
This is the standard Go async-result pattern and should be backward-compatible.
Affected methods likely include:
StartContinuousRecognitionAsyncStopContinuousRecognitionAsyncStartKeywordRecognitionAsyncStopKeywordRecognitionAsyncRecognizeOnceAsyncEnvironment
master)Additional Context
When using
PushAudioInputStreamwith continuous recognition, ifaudioConfig.Close()is called (for example viadefer) beforeStopContinuousRecognitionAsync()completes, the C-layer stop operation may hang indefinitely, making leaks even more likely.It would help to clarify cleanup order in docs, for example: