Implementing Bidirectional Streaming RPCs

1. Receiving and Sending Concurrently

Example: Bidi handler with goroutines

func (s *server) Chat(stream chatv1.ChatService_ChatServer) error {
    errCh := make(chan error, 2)
    go func() {                       // sender
        for msg := range s.broker.Subscribe() {
            if err := stream.Send(msg); err != nil { errCh <- err; return }
        }
        errCh <- nil
    }()
    go func() {                       // receiver
        for {
            m, err := stream.Recv()
            if err == io.EOF { errCh <- nil; return }
            if err != nil { errCh <- err; return }
            s.broker.Publish(m)
        }
    }()
    return <-errCh
}
RuleDetail
Send / RecvSafe to call concurrently from different goroutines
Multiple sendersNot allowed — serialize via a channel

2. Creating Receive Loop

PatternDetail
For loopUntil io.EOF or error
Forward to channelDecouple processing

3. Creating Send Loop

PatternDetail
Range over channelSend each event
Check ctx.Done()Exit on cancel

4. Coordinating Goroutines

ToolUse
errgroup.GroupFirst error cancels siblings
sync.WaitGroupWait for both to finish
Buffered errChNon-blocking error report

5. Handling Stream Completion

End FromIndicator
Client doneRecv returns io.EOF
Server doneHandler returns; client Recv gets io.EOF
Either erroredCounterpart gets status error

6. Detecting Stream Errors

SymptomCause
Send returns io.EOFServer closed stream — read final status
Recv returns non-EOFRPC failed
ctx canceledLocal/remote cancellation

7. Creating Client Bidirectional Stream

Example: Client bidi loop

stream, _ := client.Chat(ctx)
go func() {
    for input := range outbox {
        if err := stream.Send(input); err != nil { return }
    }
    _ = stream.CloseSend()
}()
for {
    m, err := stream.Recv()
    if err == io.EOF { break }
    if err != nil { return err }
    handle(m)
}

8. Implementing Request-Response Pattern

ApproachDetail
Correlation IDInclude request_id in each message
Pending mapMatch responses to waiting callers
Timeout per requestPer-request context with deadline

9. Implementing Full-Duplex Pattern

Use CasePattern
Pub/subBoth sides push independent streams
ChatEcho + broadcast model
Market dataSubscribe requests + price ticks

10. Closing Bidirectional Stream

SideMethod
Client send-sidestream.CloseSend()
ServerReturn from handler
Abort either sideCancel ctx → status Canceled