{"record":{"id":"e3174b5cea210f44","repo":"cilium/cilium","slug":"send-w","errorCode":null,"errorMessage":"send: %w","messagePattern":"send: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/xds/experimental/client/client.go","lineNumber":370,"sourceCode":"\t\t\t\treq := c.xds.nack(c.node, resp, err)\n\t\t\t\terr = trans.Send(req)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Error(\"Failed to send NACK\", logfields.Error, err)\n\t\t\t\t}\n\t\t\t\tbackoff.Wait(ctx)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// handleObserve creates a flavour-specific request based on observeRequest and sends it on given transport trans.\nfunc (c *XDSClient[ReqT, RespT]) handleObserve(trans transport[ReqT, RespT], obsReq *observeRequest) error {\n\treq := c.xds.prepareObsReq(obsReq, c.node, c.getAllResources)\n\tc.log.Debug(\"Send\", logfields.Request, req)\n\n\terr := trans.Send(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"send: %w\", err)\n\t}\n\treturn nil\n}\n\n// handleResponse creates transactions based on flavour-specific responses, applies them to cache.\nfunc (c *XDSClient[ReqT, RespT]) handleResponse(trans transport[ReqT, RespT], resp RespT) error {\n\ttransactions, err := c.xds.tx(resp, c.getAllResources)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"tx: %w\", err)\n\t}\n\tfor _, transaction := range transactions {\n\t\tc.log.Debug(\"cache TX: start\",\n\t\t\tlogfields.XDSTypeURL, transaction.typeUrl,\n\t\t\tlogfields.Upserted, transaction.updated,\n\t\t\tlogfields.Deleted, transaction.deleted,\n\t\t)\n\t\tver, updated, _ := c.cache.TX(transaction.typeUrl, transaction.updated, transaction.deleted)\n\t\tc.log.Debug(\"cache TX: end\",","sourceCodeStart":352,"sourceCodeEnd":388,"githubUrl":"https://github.com/cilium/cilium/blob/ac7b90affa4baf0642e6685319d56907b3a73a6d/pkg/xds/experimental/client/client.go#L352-L388","documentation":"handleObserve wraps a failure from trans.Send() — sending a DiscoveryRequest built from a watcher's observeRequest — as 'send: %w'. Called from the loop goroutine, a non-retriable send error terminates the whole client via 'process loop: %w'. It means the client could not write its subscribe/update request onto the xDS stream.","triggerScenarios":"A watcher registers/calls observe and loop calls handleObserve while the gRPC stream is broken or half-closed (server GOAWAY, connection reset, context canceled), so trans.Send returns an rpc error.","commonSituations":"Watcher added during server restart, stream torn down by idle timeout so the first Send after a quiet period fails, context deadline exceeded while sending on a stalled connection, sending after the server closed the stream.","solutions":["Check the wrapped gRPC code: Unavailable/Canceled are transient — make sure they are classified retriable so the client reconnects instead of dying.","Add gRPC keepalive to keep the stream alive between watcher updates.","Verify the stream is still healthy at subscribe time; re-issue the watch after the client reconnects.","Check server logs for stream closure or rejection of the request at that timestamp.","Confirm the typeUrl in the observeRequest is one the server accepts; some servers close the stream on unsupported types."],"exampleFix":"// before\nunsub := cl.AddResourceWatcher(typeUrl, cb) // stream already dead -> send fails, client stops\n// after\nunsub := cl.AddResourceWatcher(typeUrl, cb)\ngo func() { if err := <-errCh; isTransient(err) { restartClient() } }() // restart on transient send failure","handlingStrategy":"retry","validationCode":"// re-subscribe watches after reconnect instead of assuming the old stream is live\nif !clHealthy.Load() { resubscribeAll() }","typeGuard":"func isSendErr(err error) bool { return strings.HasPrefix(err.Error(), \"send:\") }","tryCatchPattern":"err := cl.Run(ctx)\nif err != nil && strings.HasPrefix(err.Error(), \"send:\") {\n\tif isTransientGRPCErr(err) { scheduleRestartWithBackoff() } else { log.Errorf(\"fatal send: %v\", errors.Unwrap(err)) }\n}","preventionTips":["Register watchers only after the client has an established stream (or tolerate a resubscribe).","Enable keepalive so quiet streams are not reaped between updates.","Handle reconnection by replaying active watches.","Check server logs for stream closure at the send timestamp."],"tags":["grpc","xds","send","watcher"],"backgroundTag":"grpc-stream-error","analyzedSha":"ac7b90affa4baf0642e6685319d56907b3a73a6d","analyzedAt":"2026-08-31T18:27:15.868Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}