Repository: grpc/grpc-go
File: stream.go (Go)

Code before:

func (cs *clientStream) CloseSend() error {
	if cs.sentLast {
		// Return a nil error on repeated calls to this method.
		return nil
	}
	cs.sentLast = true
	op := func(a *csAttempt) error {
		a.transportStream.Write(nil, nil, &transport.WriteOptions{Last: true})
		// Always return nil; io.EOF is the only error that might make sense
		// instead, but there is no need to signal the client to call RecvMsg
		// as the only use left for the stream after CloseSend is to call
		// RecvMsg.  This also matches historical behavior.
		return nil
	}
	cs.withRetry(op, func() { cs.bufferForRetryLocked(0, op, nil) })
	if len(cs.binlogs) != 0 {
		chc := &binarylog.ClientHalfClose{
			OnClientSide: true,
		}
		for _, binlog := range cs.binlogs {
			binlog.Log(cs.ctx, chc)
		}
	}
	// We don't return an error here as we expect users to read all messages
	// from the stream and get the RPC status from RecvMsg().
	return nil
}

func (cs *clientStream) finish(err error) {
	if err == io.EOF {
		// Ending a stream with EOF indicates a success.
		err = nil
	}
	cs.mu.Lock()
	if cs.finished {
		cs.mu.Unlock()
		return
	}
	cs.finished = true
	for _, onFinish := range cs.callInfo.onFinish {
		onFinish(err)
	}
	cs.commitAttemptLocked()
	if cs.attempt != nil {
		cs.attempt.finish(err)
		// after functions all rely upon having a stream.
		if cs.attempt.transportStream != nil {
			for _, o := range cs.opts {
				o.after(cs.callInfo, cs.attempt)
			}

Review comment (bug):
How about being a little more explicit why it's OK to not return an error here, but we must when doing `SendMsg`:

```suggestion
	// We don't return an error here as we expect users to read all messages
	// from the stream and get the RPC status from RecvMsg().  Note that
	// SendMsg() must return an error when one occurs so the application
	// knows to stop sending messages, but that does not apply here.
```

Code after:

func (cs *clientStream) CloseSend() error {
	if cs.sentLast {
		// Return a nil error on repeated calls to this method.
		return nil
	}
	cs.sentLast = true
	op := func(a *csAttempt) error {
		a.transportStream.Write(nil, nil, &transport.WriteOptions{Last: true})
		// Always return nil; io.EOF is the only error that might make sense
		// instead, but there is no need to signal the client to call RecvMsg
		// as the only use left for the stream after CloseSend is to call
		// RecvMsg.  This also matches historical behavior.
		return nil
	}
	cs.withRetry(op, func() { cs.bufferForRetryLocked(0, op, nil) })
	if len(cs.binlogs) != 0 {
		chc := &binarylog.ClientHalfClose{
			OnClientSide: true,
		}
		for _, binlog := range cs.binlogs {
			binlog.Log(cs.ctx, chc)
		}
	}
	// We don't return an error here as we expect users to read all messages
	// from the stream and get the RPC status from RecvMsg().  Note that
	// SendMsg() must return an error when one occurs so the application
	// knows to stop sending messages, but that does not apply here.
	return nil
}

func (cs *clientStream) finish(err error) {
	if err == io.EOF {
		// Ending a stream with EOF indicates a success.
		err = nil
	}
	cs.mu.Lock()
	if cs.finished {
		cs.mu.Unlock()
		return
	}
	cs.finished = true
	for _, onFinish := range cs.callInfo.onFinish {
		onFinish(err)
	}
	cs.commitAttemptLocked()
	if cs.attempt != nil {
		cs.attempt.finish(err)
		// after functions all rely upon having a stream.
		if cs.attempt.transportStream != nil {
			for _, o := range cs.opts {