{"record":{"id":"e8b492ab7ff4ebbc","repo":"micro/go-micro","slug":"go-micro-client-e8b492","errorCode":"go.micro.client","errorMessage":"go.micro.client","messagePattern":"go\\.micro\\.client","errorType":"exception","errorClass":"errors.Error","httpStatus":408,"severity":"error","filePath":"client/grpc/grpc.go","lineNumber":430,"sourceCode":"\n\t// check if we already have a deadline\n\td, ok := ctx.Deadline()\n\tif !ok {\n\t\t// no deadline so we create a new one\n\t\tvar cancel context.CancelFunc\n\t\tctx, cancel = context.WithTimeout(ctx, callOpts.RequestTimeout)\n\t\tdefer cancel()\n\t} else {\n\t\t// got a deadline so no need to setup context\n\t\t// but we need to set the timeout we pass along\n\t\topt := client.WithRequestTimeout(time.Until(d))\n\t\topt(&callOpts)\n\t}\n\n\t// should we noop right here?\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn errors.New(\"go.micro.client\", fmt.Sprintf(\"%v\", ctx.Err()), 408)\n\tdefault:\n\t}\n\n\t// make copy of call method\n\tgcall := g.call\n\n\t// wrap the call in reverse\n\tfor i := len(callOpts.CallWrappers); i > 0; i-- {\n\t\tgcall = callOpts.CallWrappers[i-1](gcall)\n\t}\n\n\t// return errors.New(\"go.micro.client\", \"request timeout\", 408)\n\tcall := func(i int) error {\n\t\t// call backoff first. Someone may want an initial start delay\n\t\tt, err := callOpts.Backoff(ctx, req, i)\n\t\tif err != nil {\n\t\t\treturn errors.InternalServerError(\"go.micro.client\", err.Error())\n\t\t}","sourceCodeStart":412,"sourceCodeEnd":448,"githubUrl":"https://github.com/micro/go-micro/blob/24529f140421a11a33b6999ab7944f2021cfd69c/client/grpc/grpc.go#L412-L448","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nctx := r.Context() // may already be nearly expired\nerr := client.Call(ctx, req, rsp)\n// after\nctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)\ndefer cancel()\nerr := client.Call(ctx, req, rsp)","handlingStrategy":"try-catch","validationCode":"if err := ctx.Err(); err != nil {\n    return fmt.Errorf(\"context already done before call: %w\", err)\n}","typeGuard":"func isCallTimeout(err error) bool {\n    e, ok := err.(*errors.Error)\n    return ok && e.Code == 408\n}","tryCatchPattern":"err := client.Call(ctx, req, rsp)\nif e, ok := err.(*errors.Error); ok && e.Code == 408 {\n    return fmt.Errorf(\"request timed out: %w\", ctx.Err())\n}","preventionTips":["Check ctx.Err() before RPCs in long handler chains","Scope per-call timeouts with context.WithTimeout","Keep overall request budgets aligned with downstream SLAs"],"tags":["grpc","context","timeout","deadline-exceeded","micro"],"backgroundTag":"context-deadline-exceeded","analyzedSha":"24529f140421a11a33b6999ab7944f2021cfd69c","analyzedAt":"2026-09-01T02:52:24.923Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}