Go使用gRPC流式接口传stream到Chan后发送报transport is closing错误
问题原因与解决方案
核心结论
将stream传入Channel本身不会导致stream或底层连接关闭,该操作仅传递了stream的引用,不会触发gRPC框架的资源回收逻辑。
报错根因
gRPC服务端流式RPC的处理方法Recognize的生命周期与对应stream完全绑定:只要Recognize方法执行结束返回,gRPC框架会自动关闭该stream关联的所有资源,包括底层传输链路。
你的代码中将stream传入Channel后直接return nil,相当于刚把请求抛给后台goroutine,stream就被框架主动回收了,等后台goroutine调用Send时,链路已经关闭,自然抛出transport is closing错误。
你不使用Channel时直接在Recognize方法内执行Send逻辑,方法未返回时stream保持存活,因此可以正常发送。
代码中存在的其他问题
- 不需要传递stream的指针:
AsrService_RecognizeServer本身是接口类型,接口变量传递的就是底层实现的引用,直接赋值即可,不需要取地址后再解引用,多余操作反而容易引入问题。 - 存在类型不匹配笔误:你定义的接收Chan是
ClientRequest类型,实际发送的是ASRRequest类型,需要统一Chan的类型定义。 - 没有生命周期同步机制:原逻辑没有任何机制阻塞
Recognize方法等待后台处理完成,直接返回导致资源提前释放。
修复方案
1. 扩展请求结构体新增同步通道
type ASRRequest struct { stream AsrService_RecognizeServer done chan error // 用于传递处理结果,通知原方法可以返回 } // 统一Chan定义,注意类型匹配 var ASRRequestChan = make(chan *ASRRequest, 200)
2. 修改Recognize方法,阻塞等待处理完成再返回
func (s *ScheduleServer) Recognize(stream AsrService_RecognizeServer) error { req, err := stream.Recv() if err == io.EOF || err != nil { return err } asrRequest := &ASRRequest{ stream: stream, done: make(chan error, 1), } ASRRequestChan <- asrRequest // 阻塞等待后台处理完成,不要提前返回 return <-asrRequest.done }
3. 修改后台处理逻辑,处理完成后通知原方法
func HandlRequestChan() { for r := range ASRRequestChan { Log.Infof("Chan get request info[%v]", r) var rsp RecognizeResponse rsp.Code = 0 // 替换为你的业务逻辑赋值 err := r.stream.Send(&rsp) if err != nil { fmt.Printf("Grpc write failed,err[%v]", err) } fmt.Printf("return time[%v]\n", time.Now().UnixNano()/1e6) // 通知原方法处理完成,可以返回 r.done <- err close(r.done) } }
额外注意事项
gRPC的ServerStream不是并发安全的,不要在多个goroutine中同时调用同一个stream的Send或Recv方法,如有并发操作需求需要加锁保护。
内容的提问来源于stack exchange,提问作者Yongqi Z
相关产品推荐
相关产品推荐

