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
- Check ctx.Err() before calling and skip/short-circuit if already done
- Give each RPC its own timeout via context.WithTimeout instead of inheriting a nearly-expired deadline
- Handle the 408 status on the returned *errors.Error and propagate a clearer upstream error
- 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
- Check ctx.Err() before RPCs in long handler chains
- Scope per-call timeouts with context.WithTimeout
- Keep overall request budgets aligned with downstream SLAs
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
- go.micro.client
- unable to close in time
- streamer not implemented
- connection header set to close
- ErrInvalidMessage
AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01).
Data as JSON: /api/errors/e8b492ab7ff4ebbc.
Report an issue: GitHub.