Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stream: fix calloption.After() race in finish #3672

Merged
merged 7 commits into from
Jun 11, 2020

Conversation

menghanl
Copy link
Contributor

@menghanl menghanl commented Jun 9, 2020

Race:
After cancel, two calls to stream.Finish()

func (cs *clientStream) finish(err error) {

  1. the monitor goroutine

    grpc-go/stream.go

    Line 1089 in 479df5e

    as.finish(toRPCErr(ctx.Err()))
  2. stream.RecvMsg()
    cs.finish(err)

Call 1 happens first, sets finished=true, before calling after()
Call 2 happens now, return early because finished==true. So RecvMsg() also returns
User sees error from RecvMsg(), but because 1 still hasn't call after(), peer is not set.

This fix:
after() should work on attempt instead, because after() is only called in finish,
and the stream should have been committed.

@menghanl menghanl added this to the 1.30 Release milestone Jun 9, 2020
@menghanl menghanl requested a review from dfawley June 9, 2020 17:31
Comment on lines 7150 to 7186
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
var p peer.Peer
stream, err := ss.client.FullDuplexCall(ctx, grpc.Peer(&p))
if err != nil {
t.Fatalf("_.FullDuplexCall(_) = _, %v", err)
}
if err := stream.Send(&testpb.StreamingOutputCallRequest{}); err != nil {
t.Fatalf("_ has error %v while sending", err)
}
if _, err := stream.Recv(); err != nil {
t.Fatalf("%v.Recv() = %v", stream, err)
}
cancel()
if _, err := stream.Recv(); status.Code(err) != codes.Canceled {
t.Fatalf("%v compleled with error %v, want %s", stream, err, codes.Canceled)
}
// If recv returns before call options are executed, peer.Addr is not set,
// fail the test.
if p.Addr == nil {
t.Fatalf("peer.Addr is nil, want non-nil")
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to do this in a loop to have a better chance of stimulating the race?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

rpc_util.go Outdated
if c.stream != nil {
*o.HeaderAddr, _ = c.stream.Header()
func (o HeaderCallOption) after(c *callInfo, attempt *csAttempt) {
if attempt != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we now assume this is never called with a nil attempt or attempt.s?

Copy link
Contributor Author

@menghanl menghanl Jun 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If rpc is canceled before we make the attempt (e.g. blocking on pick)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But there's only one call site (right?) and it's guarded by if attempt != nil && attempt.s != nil.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, right. Removed

@menghanl menghanl merged commit 9aa97f9 into grpc:master Jun 11, 2020
@menghanl menghanl deleted the finish_race branch June 11, 2020 01:00
@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 25, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants