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

server: return better status for context err when writing header #5292

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 9 additions & 9 deletions internal/transport/http2_server.go
Expand Up @@ -21,7 +21,6 @@ package transport
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"math"
Expand Down Expand Up @@ -53,10 +52,10 @@ import (
var (
// ErrIllegalHeaderWrite indicates that setting header is illegal because of
// the stream's state.
ErrIllegalHeaderWrite = errors.New("transport: the stream is done or WriteHeader was already called")
ErrIllegalHeaderWrite = status.Error(codes.Internal, "transport: the stream is done or WriteHeader was already called")
Copy link
Member

Choose a reason for hiding this comment

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

Please update this text since we're more precise about sending this error now. E.g. "stream.SendHeader called multiple times". (It's SendHeader at the user API level.)

// ErrHeaderListSizeLimitViolation indicates that the header list size is larger
// than the limit set by peer.
ErrHeaderListSizeLimitViolation = errors.New("transport: trying to send header list size larger than the limit set by peer")
ErrHeaderListSizeLimitViolation = status.Error(codes.Internal, "transport: trying to send header list size larger than the limit set by peer")
)

// serverConnectionCounter counts the number of connections a server has seen
Expand Down Expand Up @@ -933,9 +932,14 @@ func (t *http2Server) checkForHeaderListSize(it interface{}) bool {

// WriteHeader sends the header metadata md back to the client.
func (t *http2Server) WriteHeader(s *Stream, md metadata.MD) error {
if s.updateHeaderSent() || s.getState() == streamDone {
if s.getState() == streamDone {
return ContextErr(s.ctx.Err())
Copy link
Member

Choose a reason for hiding this comment

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

There's a small window where the state can be streamDone but the context hasn't been canceled yet. In these cases, we would be returning an INTERNAL error.

We could paper over this race by calling s.cancel() here, as we seem to do in Write. But honestly I think the right fix here is to move s.cancel() in deleteStream to the first line of both finishStream and cancelStream. And then we can remove the s.cancel() in Write. Would you mind doing this and hopefully (:crossed_fingers:) all the tests still pass?

Looking closer at Write, it looks like it has special logic to prefer returning an error that the whole connection was closed over the stream being closed. We probably want to do the same thing here. If you want to copy/paste the select, that's fine. Or if you don't mind refactoring, it looks like we need:

func (t *http2Server) streamContextErr(s *Stream) error {
	select {
	case <-t.done:
		return ErrConnClosing
	default:
	}
	return ContextErr(s.ctx.Err())
}

But I can do that as a follow-on later otherwise.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup, tests seem to all pass with this! I changed it finishStream, but it looks like it's already called in deleteStream (which closeStream calls).

Copy link
Member

Choose a reason for hiding this comment

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

It is done by deleteStream, but there is a small race, still, where the context is not canceled while the state is streamDone. So I was suggesting moving that cancelation to happen before setting the stream's state to streamDone, and also removing it from deleteStream.

}

if s.updateHeaderSent() {
dfawley marked this conversation as resolved.
Show resolved Hide resolved
return ErrIllegalHeaderWrite
}

s.hdrMu.Lock()
if md.Len() > 0 {
if s.header.Len() > 0 {
Expand Down Expand Up @@ -1062,11 +1066,7 @@ func (t *http2Server) WriteStatus(s *Stream, st *status.Status) error {
func (t *http2Server) Write(s *Stream, hdr []byte, data []byte, opts *Options) error {
if !s.isHeaderSent() { // Headers haven't been written yet.
if err := t.WriteHeader(s, nil); err != nil {
if _, ok := err.(ConnectionError); ok {
return err
}
// TODO(mmukhi, dfawley): Make sure this is the right code to return.
return status.Errorf(codes.Internal, "transport: %v", err)
return err
dfawley marked this conversation as resolved.
Show resolved Hide resolved
}
} else {
// Writing headers checks for this condition.
Expand Down
4 changes: 2 additions & 2 deletions test/end2end_test.go
Expand Up @@ -4964,8 +4964,8 @@ func testClientSendDataAfterCloseSend(t *testing.T, e env) {
}
if err := stream.SendMsg(nil); err == nil {
t.Error("expected error sending message on stream after stream closed due to illegal data")
} else if status.Code(err) != codes.Internal {
t.Errorf("expected internal error, instead received '%v'", err)
} else if status.Code(err) != codes.Canceled {
t.Errorf("expected cancel error, instead received '%v'", err)
}
return nil
}}
Expand Down