01
Tóm tắt
Vòng đời một unary RPC phía client — Invoke → newClientStream → pick → HTTP/2 wire
DRAFT
MetaGiá trị
Trace2026-07-18 · flow-trace (grpc-go) — golden-flow run đầu tiên của skill sinh bởi flow-trace-genesis v0.1.0
Repo & commitgrpc-go@7d870454 (master)module google.golang.org/grpc, go 1.25.0
Anchorcall.go:29ClientConn.Invoke
Trạng tháiDraft — chờ user chấm golden-flow gate

Generated code (stub .pb.go) gọi cc.Invoke cho mọi unary RPC. Invoke gom call options rồi đi qua interceptor chain (nếu có) trước khi vào invoke — hàm này chỉ làm đúng 3 việc: newClientStreamSendMsg(req)RecvMsg(reply).

newClientStream là nơi nặng nhất: đếm call cho channelz/idleness, validate metadata, chờ resolver trả địa chỉ đầu tiên, hỏi config selector lấy method config (timeout, retry policy, max size), chọn codec/compressor, rồi tạo attempt đầu tiên: qua pickerWrapper.pick (block tới khi balancer có picker sẵn sàng) lấy transport, mở stream HTTP/2 (http2Client.NewStream — header grpc-timeout sinh từ deadline).

SendMsg marshal + nén + kiểm tra size, buffer payload cho retry, rồi ghi frame qua transport. RecvMsg dựng decompressor theo header trả về, decode message, và đọc thêm một lần nữa để bắt EOF (cardinality check). Retry engine bọc mọi op: transparent retry khi stream chưa được server xử lý, policy retry theo service config (throttling + server pushback + backoff jitter).

⚠️
Đặc điểm đáng chú ý nhất

Với unary RPC, lỗi ghi xuống transport bị nuốt tại SendMsg (trả nil) — lỗi thật chỉ lộ ra ở RecvMsg qua status của stream (stream.go:1147-1153, comment ghi rõ "because the generated code requires it"). Ai đọc code caller mà tưởng SendMsg == nil nghĩa là đã gửi thành công sẽ hiểu sai hoàn toàn semantics.

02
Knowledge graph (máy sinh)
Subgraph flow-scoped từ GitNexus — seeds là các symbol trong bảng bước
CANDIDATE
⚠️
Candidate tier — máy sinh, không phải bằng chứng

Graph này do render/kg/extract.py sinh từ index GitNexus của grpc-go (k-hop ≤2 quanh seeds), KHÔNG qua Read từng edge. Node/cạnh sáng + nét liền = trùng seeds lấy từ bảng bước (đã Read, có file:line ở section 04); phần mờ + nét đứt = ứng viên máy gợi ý — muốn dùng làm căn cứ phải Read xác nhận trước.

Click một node để xem file:line — kéo/zoom chuột để duyệt.

Ghi chú phạm vi: single-repo (grpc-go); seeds là xương sống unary Invoke path (Invoke, newClientStream, clientStream, csAttempt, pickerWrapper, http2Client). Edge sang server-side/wire không có trong graph — xem sequence diagram (section 03) cho ranh giới HTTP/2.

03
Sequence diagram end-to-end
DETERMINISTIC
Unary Invoke — happy path (một attempt)
generated code Invoke (call.go) clientStream(stream.go) pickerWrapper+ csAttempt http2Client(internal/transport) HTTP/2 wire ① cc.Invoke(ctx, method, req, reply) — interceptor chain đứng trước invoke() ② newClientStream: idleness · metadata validate · chờ resolver · config selector · codec/compressor ③ op đầu tiên qua withRetry: getTransport + newStream ④ pick (block tới khi picker sẵn sàng) ⑤ SendMsg: marshal/nén → buffer retry → transportStream.Write (unary: lỗi write → nil!) ⑥ RecvMsg: decompressor → recv → recv lần 2 bắt EOF (cardinality) → status từ trailer đứt = goroutine nền: loopyWriter ghi frame, reader đọc frame — RPC không tự ghi socket

Mermaid — nguồn đầy đủ

sequenceDiagram
    autonumber
    participant GC as generated code
    participant IV as ClientConn.Invoke (call.go)
    participant CS as clientStream (stream.go)
    participant PW as pickerWrapper
    participant AT as csAttempt
    participant T as http2Client
    participant W as HTTP/2 wire

    GC->>IV: Invoke(ctx, method, req, reply, opts)
    Note over IV: combine(callOptions) :32<br/>unaryInt chain? :34-36
    IV->>CS: newClientStream (unaryStreamDesc)
    Note over CS: OnCallBegin :227 · metadata validate :233-246<br/>waitForResolvedAddrs :249<br/>configSelector → methodConfig :263-297<br/>timeout ctx :313-318 · codec/compressor :330-364
    CS->>AT: op = getTransport + newStream (withRetry :399-412)
    AT->>PW: pick(ctx, failfast, method) :519
    Note over PW: block trên blockingCh tới khi<br/>picker sẵn sàng :111-140
    PW-->>AT: pick{transport, result}
    AT->>T: NewStream(callHdr) :756
    Note over T: createHeaderFields :542<br/>grpc-timeout từ deadline :613
    IV->>CS: SendMsg(req)
    Note over CS: prepareMsg (marshal+nén) :973<br/>size guard :990 · bufferForRetry :1007
    CS->>AT: sendMsg :1138
    AT->>T: transportStream.Write (Last=true) :1147
    alt write lỗi (unary)
        AT-->>CS: return nil :1148-1153 (lỗi hoãn sang RecvMsg)
    end
    T-)W: loopyWriter ghi frame (goroutine :476-479)
    W-)T: reader nhận frame (goroutine :420)
    IV->>CS: RecvMsg(reply)
    CS->>AT: recvMsg (withRetry + commitAttempt :1035-1037)
    Note over AT: decompressor từ RecvCompress :1169-1187<br/>recv :1189 · recv lần 2 bắt EOF :1227-1232
    AT-->>IV: reply hoặc status error (từ trailer :1191)
    alt lỗi retryable
        CS->>CS: shouldRetry :701-789 (transparent :717 / policy+throttle :757-767<br/>pushback :734-747 · backoff jitter :776-782)
        CS->>AT: newAttemptLocked → replay buffered ops :807-850
    end
Mermaid source
mermaid
sequenceDiagram
    autonumber
    participant GC as generated code
    participant IV as ClientConn.Invoke (call.go)
    participant CS as clientStream (stream.go)
    participant PW as pickerWrapper
    participant AT as csAttempt
    participant T as http2Client
    participant W as HTTP/2 wire

    GC->>IV: Invoke(ctx, method, req, reply, opts)
    Note over IV: combine(callOptions) :32<br/>unaryInt chain? :34-36
    IV->>CS: newClientStream (unaryStreamDesc)
    Note over CS: OnCallBegin :227 · metadata validate :233-246<br/>waitForResolvedAddrs :249<br/>configSelector → methodConfig :263-297<br/>timeout ctx :313-318 · codec/compressor :330-364
    CS->>AT: op = getTransport + newStream (withRetry :399-412)
    AT->>PW: pick(ctx, failfast, method) :519
    Note over PW: block trên blockingCh tới khi<br/>picker sẵn sàng :111-140
    PW-->>AT: pick{transport, result}
    AT->>T: NewStream(callHdr) :756
    Note over T: createHeaderFields :542<br/>grpc-timeout từ deadline :613
    IV->>CS: SendMsg(req)
    Note over CS: prepareMsg (marshal+nén) :973<br/>size guard :990 · bufferForRetry :1007
    CS->>AT: sendMsg :1138
    AT->>T: transportStream.Write (Last=true) :1147
    alt write lỗi (unary)
        AT-->>CS: return nil :1148-1153 (lỗi hoãn sang RecvMsg)
    end
    T-)W: loopyWriter ghi frame (goroutine :476-479)
    W-)T: reader nhận frame (goroutine :420)
    IV->>CS: RecvMsg(reply)
    CS->>AT: recvMsg (withRetry + commitAttempt :1035-1037)
    Note over AT: decompressor từ RecvCompress :1169-1187<br/>recv :1189 · recv lần 2 bắt EOF :1227-1232
    AT-->>IV: reply hoặc status error (từ trailer :1191)
    alt lỗi retryable
        CS->>CS: shouldRetry :701-789 (transparent :717 / policy+throttle :757-767<br/>pushback :734-747 · backoff jitter :776-782)
        CS->>AT: newAttemptLocked → replay buffered ops :807-850
    end
04
Bảng bước — nguồn chân lý
Mọi node đều đã được Read trong phiên trace, kèm file:line (grpc-go@7d870454)
DETERMINISTIC

Chặng 1 — Entry & stream setup

#PackageBướcFile:lineLoại edgeGhi chú
S1grpcGenerated code gọi cc.Invoke; gom call options combine(cc.dopts.callOptions, opts)call.go:29-32callcombine cố ý không dùng append để tránh share capacity giữa các call song song call.go:40-53
S2grpcInterceptor chain: nếu dopts.unaryInt set thì bọc invokecall.go:34-36callchain dựng lúc NewClient: chainUnaryClientInterceptors clientconn.go:224,519-525unaryInt luôn đứng đầu
S3grpcinvoke = newClientStreamSendMsgRecvMsgcall.go:65-74callunaryStreamDesc (không client/server stream) call.go:63
S4grpcnewClientStream: channelz incrCallsStarted, idleness OnCallBegin, callopt OnFinish giảm đếmstream.go:214-231callcleanup khi tạo stream fail: endOfClientStream defer stream.go:217-222
S5grpcValidate metadata outgoing (key/pair) → codes.Internal nếu saistream.go:233-246call
S6grpcChờ resolver: waitForResolvedAddrs — RPC đầu tiên block tới khi có địa chỉstream.go:249clientconn.go:759call (block)trả cờ nameResolutionDelayed
S7grpcConfig selector → MethodConfig (timeout, retry policy, max size) + interceptor per-RPC nếu cóstream.go:263-297indirectionstatus code từ control plane bị giới hạn theo gRFC A54 stream.go:266-273
S8grpcnewClientStreamWithParams: ctx timeout từ mc.Timeout; callopt before hooks; max send/recv size; codecstream.go:313-334callcontext leak được document stream.go:308-312 (issue #1818)
S9grpcDựng callHdr (authority, method, content-subtype, accepted compressors) + chọn compressor (per-call UseCompressor ưu tiên hơn DialOption)stream.go:336-364callcompressor không đăng ký → codes.Internal stream.go:356-358
S10grpcNạp retryThrottler + binary loggersstream.go:385-395call

Chặng 2 — Attempt: pick transport & mở stream HTTP/2

#PackageBướcFile:lineLoại edgeGhi chú
S11grpcOp đầu tiên getTransport + newStream chạy qua withRetry, gán cs.attemptstream.go:399-412callop được buffer để replay khi retry stream.go:412
S12grpcgetTransportpickerWrapper.pick(ctx, failfast, PickInfo)stream.go:515-535indirectiondrop error từ picker đánh dấu a.drop stream.go:522-525
S13grpcpick: loop trên pickerGen, block trên blockingCh tới khi có picker; ctx done → map sang DeadlineExceeded/Canceledpicker_wrapper.go:105-174goroutine-syncErrNoSubConnAvailable → loop tiếp :159-161; failfast=false → block tiếp :172-174
S14grpccsAttempt.newStream: merge metadata từ PickResult → t.NewStreamstream.go:537-554callPreviousAttempts set từ numRetries :539
S15transporthttp2Client.NewStream(callHdr): dựng header fields (createHeaderFields), đăng ký streaminternal/transport/http2_client.go:756, :542transportdeadline → header grpc-timeout :613

Chặng 3 — Send / Recv / Retry

#PackageBướcFile:lineLoại edgeGhi chú
S16grpcSendMsg: prepareMsg (marshal + nén qua codec/compressor), guard maxSendMessageSizeResourceExhaustedstream.go:954-992callunary set sentLast ngay :968-970
S17grpcPayload Ref() + withRetry(op) + bufferForRetryLocked giữ payload cho retrystream.go:996-1012callkhông buffer thì free ngay :1010-1012
S18grpccsAttempt.sendMsgtransportStream.Write(hdr, payload, Last: true)stream.go:1138-1155transport⚠️ unary: write lỗi → return nil :1148-1153; client-stream: io.EOF :1154
S19transportFrame thực sự xuống socket qua goroutine nền: loopyWriter.run (ghi) + t.reader (đọc)internal/transport/http2_client.go:476-479, :420goroutineloopy lỗi non-IO → close conn :478-481
S20grpcRecvMsg: withRetry(recvMsg, commitAttemptLocked); kết thúc gọi finishstream.go:1025-1051callcommit = từ đây không retry op cũ nữa :1037
S21grpcrecvMsg: dựng decompressor từ header RecvCompress (một lần/stream), check compressor được phépstream.go:1169-1187callpeer nén bằng thuật toán ngoài danh sách → codes.Internal :1179-1181
S22grpcrecv decode message; io.EOF → lấy status từ trailer; không nhận message nào → cardinality violationstream.go:1189-1202call:1195-1196
S23grpcUnary đọc lần 2 — phải là EOF; nhận thêm message → cardinality violationstream.go:1225-1232callstatus cuối lấy từ transportStream.Status() :1228
S24grpcRetry engine shouldRetry: transparent (stream unprocessed) / policy theo RetryableStatusCodes + throttle + server pushback grpc-retry-pushback-ms + backoff jitter 0.8-1.2stream.go:701-789callmax attempts :767-768; disableRetry :721-723
05
Graph quan hệ function/struct
DETERMINISTIC
Màu theo layer: public API cyan · stream engine green · indirection/registry purple · transport amber · wire red
call.go (public API)registry / options stream.go (engine)internal/transport + wire ClientConn.Invoke :29 (+unaryInt chain) invoke :65 (new→Send→Recv) configSelector → MethodConfig encoding.GetCompressor/Codec (registry) newClientStream :213 newClientStreamWithParams :302 clientStream.withRetry :831SendMsg :954 · RecvMsg :1025 csAttempt :454getTransport :515 · sendMsg :1138 · recvMsg :1162 shouldRetry :701throttle · pushback · jitter · buffer replay pickerWrapper.pick :105 (blockingCh) http2Client.NewStream :756createHeaderFields :542 · grpc-timeout :613 transportStream.Write ← write :1106 goroutine: loopyWriter :476 · reader :420controlBuf → framer HTTP/2 wire (net.Conn — node lá)

Mermaid — nguồn đầy đủ

flowchart LR
    subgraph public_api["call.go — public API"]
        IV["ClientConn.Invoke :29<br/>unaryInt chain :34"] --> INV["invoke :65"]
    end
    subgraph engine["stream.go — stream engine"]
        NCS["newClientStream :213"] --> NCSP["newClientStreamWithParams :302"]
        NCSP --> CSTR["clientStream<br/>withRetry :831 · SendMsg :954 · RecvMsg :1025"]
        CSTR --> ATT["csAttempt :454<br/>getTransport :515 · sendMsg :1138 · recvMsg :1162"]
        CSTR -.retry.-> SR["shouldRetry :701<br/>throttle :764 · pushback :734 · jitter :776"]
    end
    subgraph indirection["registry / config"]
        CFG["configSelector → MethodConfig<br/>stream.go:263-297"]
        ENC["encoding registry<br/>GetCompressor/Codec"]
    end
    subgraph transport["internal/transport"]
        PW["pickerWrapper.pick :105<br/>blockingCh"]
        H2["http2Client.NewStream :756<br/>grpc-timeout :613"]
        WR["transportStream.Write ← write :1106"]
        GOR["goroutine loopyWriter :476 · reader :420"]
    end
    WIRE[("HTTP/2 wire — node lá")]

    INV --> NCS
    NCS --> CFG
    NCSP --> ENC
    ATT --> PW
    ATT --> H2
    ATT --> WR
    WR --> GOR --> WIRE
Mermaid source
mermaid
flowchart LR
    subgraph public_api["call.go — public API"]
        IV["ClientConn.Invoke :29<br/>unaryInt chain :34"] --> INV["invoke :65"]
    end
    subgraph engine["stream.go — stream engine"]
        NCS["newClientStream :213"] --> NCSP["newClientStreamWithParams :302"]
        NCSP --> CSTR["clientStream<br/>withRetry :831 · SendMsg :954 · RecvMsg :1025"]
        CSTR --> ATT["csAttempt :454<br/>getTransport :515 · sendMsg :1138 · recvMsg :1162"]
        CSTR -.retry.-> SR["shouldRetry :701<br/>throttle :764 · pushback :734 · jitter :776"]
    end
    subgraph indirection["registry / config"]
        CFG["configSelector → MethodConfig<br/>stream.go:263-297"]
        ENC["encoding registry<br/>GetCompressor/Codec"]
    end
    subgraph transport["internal/transport"]
        PW["pickerWrapper.pick :105<br/>blockingCh"]
        H2["http2Client.NewStream :756<br/>grpc-timeout :613"]
        WR["transportStream.Write ← write :1106"]
        GOR["goroutine loopyWriter :476 · reader :420"]
    end
    WIRE[("HTTP/2 wire — node lá")]

    INV --> NCS
    NCS --> CFG
    NCSP --> ENC
    ATT --> PW
    ATT --> H2
    ATT --> WR
    WR --> GOR --> WIRE
06
Validation & Rules
DETERMINISTIC
TầngVị tríRuleFile:line
MetadatanewClientStreamKey/value outgoing metadata phải hợp lệ → codes.Internalstream.go:233-246
Control planeconfig selectorStatus code từ control plane bị giới hạn danh sách gRFC A54, code lạ → codes.Internalstream.go:266-273
DeadlinenewClientStreamWithParamsmc.Timeout (service config) ép context.WithTimeout cho toàn RPCstream.go:313-318
SizeSendMsgpayload sau nén > maxSendMessageSizecodes.ResourceExhaustedstream.go:990-992
SizerecvMsgrecvdecode bị chặn bởi maxReceiveMessageSizestream.go:1189
CompressionnewClientStreamWithParamsUseCompressor chỉ định compressor chưa đăng ký → codes.Internalstream.go:353-360
CompressionrecvMsgPeer nén response bằng thuật toán ngoài AcceptCompressorscodes.Internalstream.go:1179-1181
CardinalityrecvMsgUnary không nhận được message nào (EOF + status OK) → codes.Internal "cardinality violation"stream.go:1195-1197
CardinalityrecvMsgUnary nhận >1 message (recv lần 2 không phải EOF) → codes.Internalstream.go:1225-1232
PickpickerWrapper.pickctx hết hạn khi chờ picker → map đúng DeadlineExceeded/Canceled kèm lỗi balancer gần nhấtpicker_wrapper.go:123-136
RetryshouldRetryChỉ retry code thuộc RetryableStatusCodes; throttle đếm fail; MaxAttempts chặn trần; pushback sai định dạng → abortstream.go:757-768,734-747
LifecycleSendMsgGọi sau CloseSendcodes.Internal "SendMsg called after CloseSend"stream.go:965-967
07
Business spec ngược
AI SUY LUẬN
🧠
Nhãn suy luận

Toàn bộ mục này là [AI suy luận — cần domain owner xác nhận] — với grpc-go, "domain owner" là gRFC/spec chính thức; chế độ đối chiếu PRD của skill có thể chạy trực tiếp với gRFC A6 (retry), A54 (control plane codes), A31 (config selector).

  • Capability: cho phép application code thực hiện một unary RPC đồng bộ với semantics đầy đủ: per-method config từ service config (timeout/retry/size — stream.go:263-297), interceptor chain (call.go:34-36), nén tùy chọn (stream.go:347-364), retry an toàn (chỉ khi stream chưa được server xử lý hoặc code retryable — stream.go:708-719,757-759).
  • Semantics chờ: RPC đầu tiên chờ resolver (stream.go:249), pick block khi chưa có subchannel READY trừ khi failfast (picker_wrapper.go:100-104) — nghĩa là "gọi RPC" ngầm bao gồm "chờ hạ tầng sẵn sàng", không fail ngay.
  • Chuyển trạng thái attempt: tạo attempt → committed (khi RecvMsg bắt đầu — stream.go:1037) hoặc retry (transparent/policy) → finished; onCommit báo cho config selector (stream.go:281).
  • Ngưỡng/config chi phối: service config per-method (MethodConfig), disableRetry DialOption (stream.go:721), retry throttler per-ClientConn (stream.go:386), env/DialOption về compressor và message size mặc định (stream.go:330-331).
  • Điểm cần xác nhận với spec: (1) hành vi trả nil của SendMsg unary khi write lỗi có được document ở API contract không (comment nói "generated code requires it" — stream.go:1148-1151); (2) cardinality violation trả codes.Internal — gRFC nào quy định code này thay vì Unimplemented?
08
Failure modes
Đối chiếu 6 archetype CORE + G1-G5 của PROFILE grpc-go
ARCHETYPE
#Vị tríArchetypeKịch bản: input/trạng thái → hậu quảFile:line
F1csAttempt.sendMsgG1 lỗi hoãn sang chỗ khácConn đứt giữa lúc write: unary SendMsg trả nil (không phải lỗi!) — caller tưởng đã gửi xong; lỗi thật chỉ xuất hiện ở RecvMsg qua status của stream. Code đọc if err := cs.SendMsg(req); err != nil để "xử lý lỗi gửi" là vô nghĩa với unarystream.go:1147-1155
F2newClientStreamWithParamsG3 context leak (documented)App gọi Invoke nhưng bỏ ngang không đọc kết quả: cancel của child ctx chỉ chạy khi RecvMsg lỗi / SendMsg lỗi / conn đóng — ctx (và timer nếu có timeout) sống lâu hơn cần thiết (issue #1818, comment ngay trong code)stream.go:308-323
F3SendMsg + retryG5 / #5 retry giữ payload trong RAMMessage lớn + retry policy bật: mỗi op được buffer kèm payload (bufferForRetryLocked) tới khi commit — nhiều RPC lớn đồng thời retry → memory phình; throttler chỉ hạn chế tần suất retry, không hạn chế sizestream.go:1006-1012, stream.go:764
F4pickerWrapper.pickbiến thể #2 — block vô hạnKhông set deadline + wait-for-ready (failfast=false) + balancer không bao giờ READY: pick block mãi trên blockingCh, RPC treo không lỗi — chỉ thoát khi ctx bị cancel từ ngoàipicker_wrapper.go:111-140,172-174
F5shouldRetry pushbackdữ liệu đối tác điều khiển luồngServer trả grpc-retry-pushback-ms sai định dạng hoặc nhiều giá trị → client abort retry và đếm như failure cho throttle — server cấu hình sai làm giảm retry của toàn clientstream.go:734-747
F6transport goroutines#2 fire-and-forget (ĐƯỢC XỬ LÝ)Frame thực sự ghi bởi loopyWriter goroutine, không phải RPC goroutine; loopy chết vì lỗi non-IO → close conn ngay (:478-481), reader chết → readerErrCh (:420). Ghi nhận: cơ chế lành mạnh, không phải bug — nhưng khi debug "RPC treo" phải nhìn 2 goroutine nàyinternal/transport/http2_client.go:420,476-481
F7recvMsg cardinalityguard chống server saiServer stream trả 0 hoặc >1 message cho unary → client chặn bằng codes.Internal cardinality violation thay vì im lặng nhận sai — fail loud, đúng hướngstream.go:1195-1197,1225-1232
F8combineG4 slice sharing (ĐÃ PHÒNG)Comment giải thích rõ: dùng append trên slice còn capacity sẽ share phần tử giữa các call đồng thời → race; code cấp phát mới. Pattern đáng học cho code Go kháccall.go:40-53
09
Security & Ops
AI SUY LUẬN
🧠
Nhãn suy luận

Toàn bộ mục này là [AI suy luận — cần domain owner xác nhận].

  • Credentials: per-call creds gắn qua CallOption vào callHdr.Creds (stream.go:365-367); transport-level creds nằm ngoài phạm vi trace này (thuộc Dial path). Không thấy creds bị log trong nhánh đã trace.
  • Dữ liệu nhạy cảm: khi binarylog bật, toàn bộ header + message (materialized) được log (stream.go:416-427,1013-1021,1038-1046) — môi trường có PII phải kiểm soát sink của binarylog. Stats handlers cũng nhận nguyên payload (stream.go:1156-1158,1211-1220) — plugin observability tự viết là điểm rò rỉ tiềm năng.
  • Retry/timeout: timeout per-method từ service config ép vào ctx (stream.go:313-318) và truyền xuống wire qua header grpc-timeout (internal/transport/http2_client.go:613) — server biết deadline của client. Retry có throttling per-ClientConn + server pushback + jitter 0.8-1.2 (stream.go:764,772-782) — thiết kế chống retry storm sẵn.
  • Idempotency: transparent retry chỉ khi stream Unprocessed (server chưa chạy handler — stream.go:712-719) nên an toàn; policy retry theo RetryableStatusCodes do người vận hành khai trong service config — khai code không idempotent (ví dụ retry trên UNKNOWN) là tự chuốc double-execution, thư viện không chặn hộ.
10
Ghi chú code review
Quan sát dọc đường trace — không phải review toàn diện
CƠ BẢN
  • combine() kèm comment giải thích race — mẫu tốt về "comment nói lý do, không nói cái gì" (call.go:40-53).
  • TODO còn treo: check size theo len(data) thay vì payload (stream.go:989); API merge metadata.MD vào outgoing context chưa có (stream.go:546-553).
  • Top-level Invoke deprecated vẫn export cho tương thích (call.go:55-61).
  • Semantics SendMsg unary (trả nil khi write lỗi) chỉ được giải thích bằng comment nội bộ (stream.go:1148-1151) — đáng được nêu trong doc comment public của SendMsg.
  • Cardinality violation checks (stream.go:1195-1197,1225-1232) là hardening tương đối mới — message lỗi rõ ràng, dễ grep.
  • newClientStreamWithParams dài (~120 dòng setup) nhưng tuyến tính, mỗi block có comment — đọc được; phần retry (withRetry/shouldRetry/buffer replay) là vùng phức tạp nhất, xứng đáng có diagram riêng nếu trace sâu (v2).
11
Chưa xác định / Nhánh chưa trace
CƠ BẢN
⚠️
Mục bắt buộc của template

Mọi liên kết không lần ra được phải nằm ở đây — cấm đoán rồi trình bày như fact.

  • Server-side leg (server.go processUnaryRPC, nối bằng full method name): ngoài phạm vi trace này (client-side only theo golden flow user chọn).
  • Resolver/balancer nội bộ (ccResolverWrapper, balancer_wrapper.go, quá trình sinh picker): chỉ trace đến điểm nối pickerWrapper.pick; đường resolver→balancer→picker update là nhánh riêng.
  • Transport flow control (controlBuf, loopyWriter internals, BDP, window update): node lá v1 — đã ghi nhận goroutine :420/:476 nhưng không mở hộp.
  • prepareMsg/recv internals (rpc_util.go framing, header 5-byte): dùng như hộp đen qua call site stream.go:973,1189; chưa Read file.
  • Dial path (NewClient, resolver build lần đầu, idle manager exit-idle): điểm chạm duy nhất đã trace là waitForResolvedAddrs (stream.go:249clientconn.go:759 — chưa Read thân hàm).
  • Nhánh streaming RPC (client/server streams, CloseSend :1054-1067): khác semantics unary đáng kể — flow riêng.
12
Lịch sử trace
CƠ BẢN
NgàyCommitThay đổi so với lần trước
2026-07-18grpc-go@7d870454 (master)Lần trace đầu — golden-flow gate cho skill flow-trace sinh bởi flow-trace-genesis v0.1.0