mirror of
https://github.com/zap-proto/go.git
synced 2026-10-30 15:05:00 +00:00
The call envelope has always carried Target @8, but zapgen hardcoded Target: rpc.NoTarget in every generated method, so Go could DECODE a pipelined envelope yet never ORIGINATE or RESOLVE one — pipelining was a no-op. Make the canonical Target model real on both sides. Client (origination): rpc.Session allocates unique non-zero PromiseIDs and builds Calls — Origin (Target=NoTarget) and Pipeline (Target=prior.ID). zapgen now emits a *rpc.Session-backed client: M(...) originates, MOn(on rpc.Promise) pipelines on a prior answer (payload supplied server-side). NoTarget is no longer hardcoded — it flows through one shared invoke<M> path. Both forms return the call's own rpc.Promise so chains compose. Server (resolution): rpc.Pipeliner wraps a generated Dispatch<Iface> and is the per-connection promise table: records each OK answer under its PromiseID, substitutes a resolved Target's Body for the dependent's Payload before dispatch, QUEUES a dependent whose Target hasn't resolved yet until it does, and REFUSES (StatusBadRequest, never hangs) a dependent whose Target answered non-OK or was Finished. Finish(id) bounds the table. Wire-compatible: Target=NoTarget is the non-pipelined call, so a Pipeliner and a plain dispatcher are byte-identical on the wire; a non-pipelining peer (any other runtime) interoperates trivially. Proven end-to-end in rpc/pipeline_test.go (auth->getResource): single-pass resolution, server-side queuing when the dependent arrives first, a 3-deep A->B->C chain, non-OK/Finished targets refused, Finish waking a parked dependent, and Target riding on the wire byte-for-byte. Full suite green under -race. Docs: README + LLM.md describe the one canonical model and the Rust capnp PromisedAnswer boundary honestly.