Go实现Google Cloud Speech无限流识别 流重启后无法接收结果
Go实现Google Speech无限流识别重启无响应问题排查
核心根因
代码存在6个直接导致流重启后无结果的致命问题:
- 接收协程错误处理逻辑失效:
startReceive中收到io.EOF时直接continue,会触发无限死循环。流正常关闭后Recv()会持续返回io.EOF,协程永远卡在此处,既不会响应重启退出信号,也会持续占用CPU资源。 - 协程通信机制存在死锁风险:重启信号使用无缓冲通道,但接收协程99%的时间阻塞在gRPC流的
Recv()调用上,根本不会执行到select逻辑读取通道信号,会导致重启流程永久阻塞在信号发送步骤。 - 共享缓冲区并发不安全:
bytes.Buffer本身非线程安全,WebSocket读协程写缓冲区、发送协程读缓冲区的操作没有加锁,会出现数据竞争、音频帧损坏、数据丢失问题。 - 残留音频截断逻辑错误:使用平均码率换算字节偏移切分残留OPUS音频,OPUS是变码率编码,这种切分方式100%会切到音频帧中间,发往服务端的是破损的二进制数据,服务端会直接丢弃后续所有请求,不会返回任何识别结果。
- 流资源未正确释放:旧流仅调用
CloseSend(),既不读取剩余响应直到io.EOF,也没有取消流绑定的context,会导致gRPC连接状态异常、资源泄漏,新流复用客户端时容易出现隐形错误。 - 无意义的启动延迟:创建新流前强制sleep 1秒,会导致音频断流,服务端收到不连续的音频时会重置识别会话,无法返回结果。
另外代码中常量定义和注释不符:MAX_STREAM_DURATION注释标注为秒,实际判断时用毫秒单位比较,60000毫秒对应1分钟,和Google Speech单流5分钟的限制不匹配,虽然不直接触发bug但容易引发后续逻辑错误。
修复方案
按以下顺序修改代码即可解决问题:
- 修正接收协程错误处理
移除io.EOF分支的continue逻辑,只要Recv()返回任何错误(包括io.EOF),直接记录最终时间戳后退出协程,不要循环重试:resp, err := (*stream).Recv() if err != nil { fmt.Printf("Stream exited: %v\n", err) *fret = finalRequestEndTime return } - 替换协程生命周期控制逻辑
不要用无缓冲通道传递重启信号,每个流创建独立的带cancel的context,重启时直接调用旧流的cancel函数,强制让阻塞的Send/Recv调用立刻返回错误,等待两个协程通过wg.Wait()完全退出后,再创建新流。 - 给共享缓冲区加互斥锁
声明全局sync.Mutex保护音频缓冲区,所有读写缓冲区的操作(WebSocket写入、发送协程读取)都要加锁,避免数据竞争。 - 修正残留音频重发逻辑
废弃平均码率算偏移的逻辑,每次收到final识别结果后,记录当前缓冲区的读取位置,流重启时把最后一次final结果之后收到的完整未截断音频数据全部重发到新流,不要做任何字节切片操作,保证OPUS帧完整性。单流重启阈值设为290秒(留10秒余量,不卡满5分钟限制),提前200ms触发重启,避免丢字。 - 移除所有创建流/客户端前的1秒sleep,重启流程不需要额外等待,只要旧协程完全退出即可立刻创建新流。
- 修正常量定义,将
MAX_STREAM_DURATION设为290000(对应290秒,单位毫秒),和实际判断逻辑对齐。
内容的提问来源于stack exchange,提问作者AndreSky
相关产品推荐
相关产品推荐

