drpcquic: add DRPC-over-QUIC transport#79
Closed
eshwarsriramoju wants to merge 18 commits into
Closed
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…st harness Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er transport) mode
…r (ServeMultiplexed), and Dial/Listen/Serve
…r-death, no-log, leak)
Add a QUIC Server type whose Serve(ctx, *Listener) method mirrors (*drpcserver.Server).Serve, so it fits callers that hold a server and call Serve(ctx, lis). The free Serve stays as a wrapper. Add ListenPacket for a caller-owned UDP socket.
The accept loop read each stream's invoke before accepting the next, so a stream slow to send its invoke blocked every later stream on the connection. Split AcceptStream (loop) from the invoke read (per-stream goroutine), isolate per-stream parse errors, and order shutdown Cancel->Close->Wait so a stream stalled mid-invoke can't hang drain.
mapQUICError let context.DeadlineExceeded fall into the net.Error timeout catch-all, wrapping it as ClosedError so ToRPCErr returned Unavailable. Return the bare sentinel before the catch-all; ToRPCErr matches it by value and yields codes.DeadlineExceeded.
…rns nil Mirror ServeOne's TLS peer-info: read the verified chain off the QUIC connection and set drpcctx.PeerConnectionInfo before ServeMultiplexed so handlers (auth) see peer identity. Serve now returns nil on clean stop.
Rewrite DRPC-over-QUIC from the "pipe adapter" design to the "application
adapter" design: instead of wrapping each QUIC stream as a drpc.Transport and
driving it through a QUICManager + drpcstream state machine, a QuicConn
implements drpc.Conn and a QuicStream implements drpc.Stream directly over
quic-go. drpc's multiplexing manager is bypassed entirely; only its framing,
encoding, error and metadata helpers are reused.
Each RPC opens its own native QUIC stream, so there is no shared reader, no
semaphore, no demux, and no per-RPC readLoop/watcher goroutines. Cancellation
uses the QUIC stream's native context on the server and a single context.AfterFunc
on the client.
Removed pipe adapter:
- drpc.MultiplexedTransport
- drpcmanager/quic_manager.go
- drpcquic transport/server/listener/options/error/tls adapter files
- drpcstream NewForReader/readLoop/watcher
- drpcconn NewFromMultiplexed
- drpcserver/multiplexed.go
Added:
- drpcquic/{doc,wire,conn,stream}.go
- drpcserver/server_quic.go
quic_test.go covers unary, server-streaming, error-code survival, metadata,
20-way concurrency, server-observes-client-cancel, and a >4 MiB message.
go build/vet clean; drpcquic tests pass under -race; TCP-path suites still pass.
Dial and Listen passed a nil *quic.Config, so quic-go's defaults applied — most importantly MaxIncomingStreams=100. Because every drpc RPC opens its own QUIC stream, a connection carrying more than 100 concurrent RPCs makes the peer's OpenStreamSync block on stream credits, stalling latency-sensitive RPCs (e.g. connection heartbeats) until they time out and the connection is torn down and re-dialed. Under CockroachDB node-to-node load this shows up as "conn heartbeat timed out" churn and "Application error 0x0" on in-flight RPCs. Add quicConfig() and pass it from Dial/DialWithOptions/Listen/ListenPacket: - MaxIncomingStreams 1<<16 (well above realistic per-connection concurrency), - KeepAlivePeriod 15s / MaxIdleTimeout 30s (the QUIC idle timeout is connection-scoped; keepalive stops a quiet period from dropping every multiplexed stream). go build/vet clean; go test ./drpcquic/ -race passes.
A minimal end-to-end example of the stream-per-QUIC-stream API. The server registers EchoService on a drpcmux and serves it with drpcserver.(*Server).ServeQuic over a drpcquic.Listen listener; the client dials with drpcquic.Dial (whose *QuicConn is itself a drpc.Conn, so no drpcconn wrapping) and calls Echo. QUIC requires TLS, so the server loads a self-signed cert and the client uses InsecureSkipVerify; the drpc-quic ALPN is forced inside drpcquic.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
drpcquic: add DRPC-over-QUIC transportAdd
drpcquic, a QUIC-based transport for DRPC.DRPC multiplexes logical streams above the transport. Over TCP, all logical
streams share one ordered byte stream, so a single lost segment stalls every
stream on the connection (transport-layer head-of-line blocking), which cannot
be fixed in application code over one TCP connection. QUIC provides independent,
flow-controlled streams, so mapping each
drpcstream onto its own QUIC streamremoves this by construction: loss on one stream cannot stall the others.
Design: one
drpc.Streammaps to one nativequic.Stream, with no manager. The TCPpath uses
drpcmanagerto demux frames by stream id over a shared reader/writer;QUIC already demultiplexes streams, so that machinery is bypassed.
QuicConnwraps*quic.Connand implementsdrpc.Conn;QuicStreamwraps*quic.Streamandimplements
drpc.Streamdirectly.Includes:
Dial/DialWithOptions(one QUIC connection per peer, one stream perInvoke), forcing thedrpc-quicALPN over a caller-supplied TLS config.drpcserver.ServeQuicplus adrpcquic.ServerwithServe(ctx, lis),Listen, andListenPacket, reusing the standarddrpchandler registration.quic-goerror mapping, context deadline tocodes.DeadlineExceeded, peer-cert injection into the served context, andclean-shutdown handling.
MaxIncomingStreams, keepalive / idle timeout).goroutine-leak), a
cmd/quicdemo, and anexamples/drpc-quicecho service.Depends on
quic-go v0.59.1and requires Go1.25.This is an experimental transport.