micro/go-micro · error · errors.Error

go.micro.client

go.micro.client

Error message

go.micro.client

What it means

In client/grpc/grpc.go:430, the Call method checks whether the passed context is already done before issuing the RPC. If ctx has been cancelled or its deadline exceeded, it returns a micro error with code "go.micro.client" and status 408 (request timeout) wrapping the context error. This is a fast-path noop guard.

Source

Thrown at client/grpc/grpc.go:430

	// check if we already have a deadline
	d, ok := ctx.Deadline()
	if !ok {
		// no deadline so we create a new one
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout)
		defer cancel()
	} else {
		// got a deadline so no need to setup context
		// but we need to set the timeout we pass along
		opt := client.WithRequestTimeout(time.Until(d))
		opt(&callOpts)
	}

	// should we noop right here?
	select {
	case <-ctx.Done():
		return errors.New("go.micro.client", fmt.Sprintf("%v", ctx.Err()), 408)
	default:
	}

	// make copy of call method
	gcall := g.call

	// wrap the call in reverse
	for i := len(callOpts.CallWrappers); i > 0; i-- {
		gcall = callOpts.CallWrappers[i-1](gcall)
	}

	// return errors.New("go.micro.client", "request timeout", 408)
	call := func(i int) error {
		// call backoff first. Someone may want an initial start delay
		t, err := callOpts.Backoff(ctx, req, i)
		if err != nil {
			return errors.InternalServerError("go.micro.client", err.Error())
		}

View on GitHub (pinned to 24529f1404)

Solutions

  1. Check ctx.Err() before calling and skip/short-circuit if already done
  2. Give each RPC its own timeout via context.WithTimeout instead of inheriting a nearly-expired deadline
  3. Handle the 408 status on the returned *errors.Error and propagate a clearer upstream error
  4. Increase the overall request deadline if the operation legitimately needs more time

Example fix

// before
ctx := r.Context() // may already be nearly expired
err := client.Call(ctx, req, rsp)
// after
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
err := client.Call(ctx, req, rsp)
Defensive patterns

Strategy: try-catch

Validate before calling

if err := ctx.Err(); err != nil {
    return fmt.Errorf("context already done before call: %w", err)
}

Type guard

func isCallTimeout(err error) bool {
    e, ok := err.(*errors.Error)
    return ok && e.Code == 408
}

Try / catch

err := client.Call(ctx, req, rsp)
if e, ok := err.(*errors.Error); ok && e.Code == 408 {
    return fmt.Errorf("request timed out: %w", ctx.Err())
}

Prevention

When it happens

Trigger: Calling client.Call with a context that is already cancelled or past its deadline before the request is sent — e.g. a parent handler's deadline expired, or ctx was cancelled by an earlier failure.

Common situations: Chained RPCs under one request deadline where an earlier step consumed the budget; forgetting to apply a fresh timeout for a subsequent call; test contexts cancelled prematurely.

Related errors


AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01). Data as JSON: /api/errors/e8b492ab7ff4ebbc. Report an issue: GitHub.