geektutu/7days-golang · error

%s

Error message

%s

What it means

In gee-rpc/day4-timeout's receive loop, a failure reading the response body sets the Call's Error to "reading body <cause>" and terminates the connection and every pending call. The timeout additions on the client do not change this path: it is still a broken/undecodable response stream.

Source

Thrown at gee-rpc/day4-timeout/client.go:148

		}
	}
}

func (client *Client) receive() {
	var err error
	for err == nil {
		var h codec.Header
		if err = client.cc.ReadHeader(&h); err != nil {
			break
		}
		call := client.removeCall(h.Seq)
		switch {
		case call == nil:
			// it usually means that Write partially failed
			// and call was already removed.
			err = client.cc.ReadBody(nil)
		case h.Error != "":
			call.Error = fmt.Errorf(h.Error)
			err = client.cc.ReadBody(nil)
			call.done()
		default:
			err = client.cc.ReadBody(call.Reply)
			if err != nil {
				call.Error = errors.New("reading body " + err.Error())
			}
			call.done()
		}
	}
	// error occurs, so terminateCalls pending calls
	client.terminateCalls(err)
}

// Go invokes the function asynchronously.
// It returns the Call structure representing the invocation.
func (client *Client) Go(serviceMethod string, args, reply interface{}, done chan *Call) *Call {
	if done == nil {

View on GitHub (pinned to cf36443821)

Solutions

  1. Align ConnectTimeout/HandleTimeout on the server and client so neither side drops the connection mid-response
  2. Pass a correct non-nil pointer reply matching the server's reply type
  3. Check server logs for panics or forced connection closes
  4. Retry with a fresh Dial after this error; the old client is unusable

Example fix

// before
client, _ := geerpc.Dial("tcp", addr, opt) // opt timeouts too small
err := client.Call("Foo.Sum", req, reply)
// after
opt := &geerpc.Option{ConnectTimeout: 5 * time.Second, HandleTimeout: 10 * time.Second}
client, err := geerpc.Dial("tcp", addr, opt)
err = client.Call("Foo.Sum", req, &reply)
Defensive patterns

Strategy: retry

Validate before calling

opt := &geerpc.Option{ConnectTimeout: 5 * time.Second, HandleTimeout: 10 * time.Second}
if opt.HandleTimeout <= opt.ConnectTimeout { /* server may close mid-response; adjust */ }

Type guard

func replyIsPointer(v interface{}) bool {
	rv := reflect.ValueOf(v)
	return rv.IsValid() && rv.Kind() == reflect.Ptr && !rv.IsNil()
}

Try / catch

if err := client.Call("Foo.Sum", req, reply); err != nil {
	if strings.HasPrefix(err.Error(), "reading body") {
		client, err = geerpc.Dial("tcp", addr, opt)
		if err == nil { err = client.Call("Foo.Sum", req, reply) }
	}
}

Prevention

When it happens

Trigger: A Call/Go on a day4-timeout client gets a header but ReadBody fails: server closed the connection after the header, response truncated (possibly by a timeout handler closing the conn), or reply decode type mismatch.

Common situations: Server-side handler timeout closes the socket mid-response; client-side Call timeout abandons the call while the server is still writing; reply pointer type mismatch; server panic during serialization.

Related errors


AI-assisted analysis of geektutu/7days-golang@cf36443821 (2026-09-03). Data as JSON: /api/errors/b1d296d81eedfb0e. Report an issue: GitHub.