{"record":{"id":"d4bf2defabe2f2a3","repo":"dgraph-io/dgraph","slug":"relay-final-upstream-w","errorCode":null,"errorMessage":"relay final upstream: %w","messagePattern":"relay final upstream: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"worker/import.go","lineNumber":433,"sourceCode":"\t\t\tif err := out.Send(&api.StreamExtSnapshotRequest{Pkt: req.Pkt}); err != nil && !errors.Is(err, io.EOF) {\n\t\t\t\treturn fmt.Errorf(\"send done downstream(%d): %w\", groupId, err)\n\t\t\t}\n\t\t\t_ = out.CloseSend()\n\n\t\t\t// Drain downstream and relay upstream until Finish=true.\n\t\t\tfor {\n\t\t\t\tif err := ctx.Err(); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tresp, err := out.Recv()\n\t\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\t\treturn fmt.Errorf(\"downstream(%d) closed before Finish=true\", groupId)\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"recv final downstream(%d): %w\", groupId, err)\n\t\t\t\t}\n\t\t\t\tif err := in.Send(resp); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"relay final upstream: %w\", err)\n\t\t\t\t}\n\t\t\t\tif resp.Finish {\n\t\t\t\t\tglog.Infof(\"[import] [forward %d -> %d] finish\", currentGroup, groupId)\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Normal data chunk: send -> wait ack -> send upstream ack.\n\t\tif err := out.Send(&api.StreamExtSnapshotRequest{Pkt: req.Pkt}); err != nil {\n\t\t\treturn fmt.Errorf(\"send data downstream(%d): %w\", groupId, err)\n\t\t}\n\t\tif _, err := out.Recv(); err != nil {\n\t\t\treturn fmt.Errorf(\"ack data downstream(%d): %w\", groupId, err)\n\t\t}\n\t\tif err := in.Send(&api.StreamExtSnapshotResponse{}); err != nil {\n\t\t\treturn fmt.Errorf(\"send ack upstream: %w\", err)\n\t\t}","sourceCodeStart":415,"sourceCodeEnd":451,"githubUrl":"https://github.com/dgraph-io/dgraph/blob/759e242be62c91f8d084da06ad0c8d21256d9c07/worker/import.go#L415-L451","documentation":"After receiving a response from the leader during the final drain, the relay must echo it back to the import client via in.Send(resp). If that send fails, the upstream client can no longer be informed of progress and piping aborts with this wrapped error.","triggerScenarios":"in.Send(resp) errors because the import client disconnected, cancelled its context, or its stream was torn down by a transport error while the leader was still responding.","commonSituations":"Client timed out waiting during the leader's slow final flush and cancelled; client process died near the end of a long import; proxy between client and alpha closed the stream.","solutions":["Check the wrapped error: Canceled indicates the client gave up — increase client timeout for large snapshots","Re-run the import with a stable client connection","Remove idle-timeout proxies on the client-to-alpha path","Keep the client alive until Finish=true is received"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// client must stay connected for the whole final phase\nctx, cancel := context.WithTimeout(context.Background(), generousDeadlineForSnapshot)\ndefer cancel()","typeGuard":"func clientCancelled(err error) bool { return status.Code(err) == codes.Canceled }","tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"relay final upstream\") {\n    if clientCancelled(errors.Unwrap(err)) {\n        // client gave up: increase client timeout and re-import\n    }\n}","preventionTips":["Do not cancel the import client while the leader is finalizing","Set client timeouts well above expected finalization time","Keep proxies from severing the client stream near the end","Log Finish receipt client-side to confirm full completion"],"tags":["grpc","streaming","relay","io"],"backgroundTag":"upstream-stream-send-failed","analyzedSha":"759e242be62c91f8d084da06ad0c8d21256d9c07","analyzedAt":"2026-09-01T14:42:12.034Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}