From 0de896bce38210f161f5896f3cd7bd06fe5d2288 Mon Sep 17 00:00:00 2001 From: Doug Fawley Date: Wed, 19 Sep 2018 09:02:30 -0700 Subject: [PATCH 1/3] L37: Go Custom Transports --- L37-go-custom-transports.md | 415 ++++++++++++++++++++++++++++++++ L37_graphics/client_diagram.png | Bin 0 -> 56838 bytes L37_graphics/client_diagram.svg | 212 ++++++++++++++++ 3 files changed, 627 insertions(+) create mode 100644 L37-go-custom-transports.md create mode 100644 L37_graphics/client_diagram.png create mode 100644 L37_graphics/client_diagram.svg diff --git a/L37-go-custom-transports.md b/L37-go-custom-transports.md new file mode 100644 index 000000000..b3178464d --- /dev/null +++ b/L37-go-custom-transports.md @@ -0,0 +1,415 @@ +L37: Go Custom Transports +---- +* Author(s): dfawley +* Approver: a11r +* Status: Draft +* Implemented in: Go +* Last updated: 2018-09-20 +* Discussion at: + +## Abstract + +Create an API to allow custom client and server transports in grpc-go. + +## Background + +grpc-go currently supports only a single, hardcoded HTTP/2 transport layer. +However, the gRPC semantics and API can be transmitted by alternate means, +e.g. HTTP/1.1. Some environments, like WebAssembly, can't support grpc-go's +full transport, but could support an alternate version of it. It is also +possible to create different types of connections besides raw byte transports, +for example an in-process transport capable of copying messages instead of +serializing and deserializing. + +Several grpc-go github issues have been filed requesting this feature; all have +been consolidated to [a canonical +issue](https://github.com/grpc/grpc-go/issues/906). + +## Proposal + +The main goal of this proposal is to allow for users to inject custom transports +for clients and servers. Some additional design goals of this proposal: + +1. Transport and stream APIs should expose only gRPC semantics as defined in the + [Call Semantics Specification](https://github.com/grpc/grpc/pull/15460) and + hide HTTP/2 semantics and grpc wire protocol semantics). +1. Transports should be message-based, not byte-based, to facilitate an + in-process transport that uses `proto.Clone()` for efficiency. + - Note that retry support requires byte-based support from all transports, + because we need to cache multiple sent messages, which our streaming API + does not make possible without serialization. +1. Unexpected asynchronous events should be delivered by callbacks, not + channels. Channels may be more idiomatic for Go, but require a goroutine to + monitor them, which leads to worse performance (in the case of streams) and + more complex code. See [#2219](https://github.com/grpc/grpc-go/pull/2219) + which simplifies our transport monitoring by using callbacks. Sending and + receiving data should remain synchronous to mirror grpc's external API. +1. The API for creating connections will be based upon transports, not + `net.Conn`s. How to connect (e.g. `net.Dial`) is an implementation detail of + the transport. Likewise, credentials handshaking must happen within the + transport. +1. No stable external APIs may be changed (one experimental API must be + changed). + +This feature consists of the following components: + +1. (Client) Allow resolvers and balancers to specify different address types. +1. (Client) Define a method for selecting which transport to use for each + address type. +1. (Server) Define a new API for serving with *transport* listeners instead of + `net.Listener`s. +1. (Client and Server) Design a simple API for transports and streams. + +### Client Address Types + +The following diagram shows how grpc-go currently creates a transport and +streams within the transport on the client-side. + +![Diagram](L37_graphics/client_diagram.png) + +[Link to SVG file](L37_graphics/client_diagram.svg) + +First the target given to `grpc.Dial` is passed to the resolver with the scheme +identified by the target. This converts the name into a list of addresses. +These addresses are then given to the load balancer, which creates `SubConn`s +containing these addresses. The `SubConn` manages itself and creates +`ClientTransport`s using the addresses by calling a hard-coded constructor. + +Rather than using a hard-coded constructor, the `resolver` package's `Address` +struct will be extended to contain a string field to indicate the type of the +address. There is already a `Type` field to indicate whether addresses are +gRPCLB servers or backend addresses. To avoid confusion, this will be renamed +to `ServerType`, and the new field will be named `TransportType`. + +Summary of API changes proposed: + +- `resolver.Address`: rename `Type` to `ServerType`; add `TransportType string` + +### Client Transport Selection + +When the `SubConn` receives an `Address` from the balancer, it will look up the +`TransportType` name in a global registry similar to the registries for +resolvers and balancers. This will reference a `client.TransportBuilder` +(defined below) that allows the `SubConn` to create a transport for the address. + +Summary of API changes proposed: + +- In the new `transport/client` package, include `Register` and `Get` methods, + defined below. + +### Server Transport Listeners + +grpc-go currently requires `net.Listener`s to be passed to `grpc.Serve` in order +to begin serving RPCs. This must be changed, as some types of transports will +not be backed by a traditional `net.Conn`. Instead, a new +`Server.ServeTransport` function will be defined that listens for new `Stream`s. +The user is responsible for managing connections within the transport listener. + +Summary of API changes proposed: + +- New function in `grpc` package: + ```go + // ServeTransport accepts incoming Streams from the TransportListener, + // creating a new service goroutine for each and handling them using the + // registered handlers. ServeTransport returns when all listeners and streams + // have been stopped. Returns the error from tl. tl will be closed if the + // Server was already stopped. + func (s *Server) ServeTransport(tl *server.TransportListener) error + ``` + +### Transport and Stream APIs + +The current `ClientTransport` and `Stream` API communicates with the `Stream` by +calling `Read` on the `Stream` but `Write` on the `Transport` (passing in the +`Stream`). There are also some shared responsibilities of managing transports +and streams between the transport and the grpc layer. This design attempts to +correct some of these problems and also provide an API surface that aligns with +the [Call Semantics Specification](https://github.com/grpc/grpc/pull/15460). + +The following package structure (under `google.golang.org/grpc/`) will be +created: + +- `transport`: Definition of types shared between client and server +- `transport/client`: Definition of client-side transport and stream +- `transport/server`: Definition of server-side transport and stream + +```go +package transport + +// OutgoingMessage is a message to be sent by gRPC. +type OutgoingMessage interface { + // Marshal marshals to a byte buffer and returns information about the + // encoding, or an error. Repeated calls to Marshal must always return the + // same values. + Marshal() ([]byte, *MessageInfo, error) +} + +// IncomingMessage is a message to be received by gRPC. +type IncomingMessage interface { + // Unmarshal unmarshals from the scatter-gather byte buffer given. + Unmarshal([][]byte, *MessageInfo) error +} + +// MessageInfo contains details about how a message is encoded. +type MessageInfo struct { + // Encoding is the message's content-type encoding (e.g. "proto"). + Encoding string + // Compressor is the compressor's name or the empty string if compression + // is disabled. + Compressor string +} +``` + + +```go +package client + +// TransportBuilder constructs Transports connected to addresses. +type TransportBuilder interface { + // Build begins connecting to the address. It must return a Transport that + // is ready to accept new streams or an error. + Build(context.Context, resolver.Address, TransportMonitor, TransportBuildOptions) (Transport, error) +} + +type TransportBuildOptions struct { + // Options contains opaque Transport configuration. + // May contain: PerRPCCredentials, TransportCredentials, + // keepalive.ClientParameters, etc. + Options []interface{} +} + +// A TransportMonitor is a monitor for client-side transports. +type TransportMonitor interface { + // Connected reports that the Transport is fully connected - i.e. the + // remote server has confirmed it is a gRPC server. + // + // May only be called once. + Connected() + // OnError reports that the Transport has closed due to the error provided. + // Existing streams may or may not continue. + // + // Once called, no further calls in the TransportMonitor are valid. + OnError(error) +} + +// A Transport is a client-side gRPC transport. It begins in a +// "half-connected" state where the client may opportunistically start new +// streams by calling NewStream. Some clients will wait until the +// TransportMonitor's Connected method is called. +type Transport interface { + // NewStream begins a new Stream on the Transport. Blocks until sufficient + // stream quota is available, if applicable. If the Transport is closed, + // returns an error. + NewStream(context.Context, NewStreamOptions) (Stream, error) + + // GracefulClose closes the Transport. Outstanding and pending Streams + // created by NewStream continue uninterrupted and this function blocks + // until the Streams are finished. Close may be called concurrently. + GracefulClose() + + // Close closes the Transport. Outstanding and pending Streams created by + // NewStream are canceled. + Close() + + // Info returns information about the transport's current state. + Info() TransportInfo +} + +// TransportInfo contains information about the transport's current state. All +// information is optional. +type TransportInfo struct { + RemoteAddr net.Addr // the address of the server (typically an IP/port). + IsSecure bool // if set, WithInsecure is not required and Per-RPC Credentials are allowed. + AuthInfo credentials.AuthInfo +} + +// NewStreamOptions defines information used to begin a new stream. +type NewStreamOptions struct { + Method string // required: remote server's RPC method + Authority string // for transports supporting virtual hosting + Metadata metadata.MD // optional + MaxRecvMsgSize *int // if non-nil, maximum size for a received message + + Options []interface{} // stream-specific options, e.g. content sub-type +} + +// Stream defines all necessary RPC functions. +type Stream interface { + // SendMsg queues the message m to be sent by the Stream and returns true + // unless the Stream terminates before queuing the message. + SendMsg(m transport.OutgoingMessage, opts StreamSendMsgOptions) bool + + // RecvHeader blocks until the Stream receives the server's header and then + // returns it. Returns nil if the Stream terminated without a valid + // header. Repeated calls must return the same header. + RecvHeader() *ServerHeader + + // RecvMsg receives the next message on the Stream into m and returns true + // unless the Stream terminates before a full message is received. + RecvMsg(m transport.IncomingMessage) bool + + // RecvTrailer blocks until the Stream receives the server's trailer and + // then returns it. Returns a synthesized trailer containing an + // appropriate status if the RPC terminates before receiving a trailer from + // the server. Repeated calls must return the same trailer. + // + // If all messages have not be retrieved from the stream before calling + // RecvTrailer, subsequent calls to RecvMsg should immediately fail. This + // is to prevent the status in the trailer from changing as a result of + // parsing the messages. + RecvTrailer() Trailer + + // Cancel unconditionally cancels the RPC. Queued messages may or may not + // be sent. If the stream does not already have a status, the one provided + // (which must be non-nil) is used. + Cancel(*status.Status) + + // Info returns information about the stream's current state. + Info() StreamInfo +} + +// StreamSendMsgOptions defines options used by Stream.SendMsg. +type StreamSendMsgOptions struct { + CloseSend bool // set if this is the final outgoing message on the stream. + Options []interface{} // E.g. whether to compress this message. +} + +// Trailer defines the information in the server's trailer. +type Trailer struct { + Status *status.Status + Metadata metadata.MD +} + +// ServerHeader contains header data sent by the server. +type ServerHeader struct { + Metadata metadata.MD +} + +// StreamInfo contains information about the stream's current state. +// +// All information is optional. Fields not supported by a Stream returning +// this struct should be nil. If a transport does not support all features, +// some grpc features (e.g. transparent retry or grpclb load reporting) may not +// work properly. +type StreamInfo struct { + // DataReceived is true iff the client received any data for this stream + // from the server (e.g. partial header bytes), false if the stream ended + // without receiving any data, or nil if data may still be received. + DataReceived *bool + + // Unprocessed is true if the server has confirmed the application did not + // process this stream*, false if the server sent a response indicating the + // application may have processed the stream, or nil if it is uncertain. + // + // In HTTP/2, this is true if a RST_STREAM with REFUSED_STREAM is received + // or if a GOAWAY including this stream's ID is received. + Unprocessed *bool +} + +func Register(name string, ctb TransportBuilder) + +func Get(name string) TransportBuilder // May be moved to internal. +``` + +```go +package server + +// TransportListener provides Streams to be handled by grpc. +type TransportListener interface { + // Accept blocks until a new Stream is created. Returns an error when the + // listener should stop being used. + Accept() (Stream, error) + + // GracefulClose causes the TransportListener to stop accepting new + // incoming streams, and returns when all outstanding streams have + // completed. Pending and future calls to Accept should return a non-nil + // error. + GracefulClose() + + // Close immediately closes all outstanding connections and streams. + // Pending and future calls to Accept should return a non-nil error. + Close() +} + +// Header defines the fields set in the client header. +type ClientHeader struct { + MethodName string + Metadata metadata.MD +} + +// Trailer defines the information contained in the server's trailer. +type Trailer struct { + Status status.Status + Metadata metadata.MD +} + +// Stream defines all necessary RPC functions. +type Stream interface { + // RecvHeader returns the client's header. + RecvHeader() ClientHeader + + // RecvMsg receives the next message on the Stream into m and returns true + // unless the Stream terminates before a full message is received. + RecvMsg(m transport.IncomingMessage) bool + // SendHeader immediately sends the header to the client. Returns true + // unless the Stream has encountered an error. + SendHeader(Header) bool + // SendMsg queues the message m to be sent by the Stream and returns true + // unless the Stream terminates before queuing the message. + SendMsg(m transport.OutgoingMessage, opts StreamSendMsgOptions) bool + // Close terminates the RPC and sends the trailer provided. All subsequent + // operations on the Stream except Info should fail. + Close(Trailer) + + // Info returns information about the stream's current state. + Info() StreamInfo +} + +// Header contains header data sent by the server. +type Header struct { + Metadata metadata.MD +} + +// StreamInfo contains information about the stream's current state. All +// information is optional. +type StreamInfo struct { + // RemoteAddr is the address of the client (typically an IP/port). + RemoteAddr string +} +``` + + +## Rationale + +Alternatives considered: + +- Hardcode other transports, like the in-memory transport. + + This does not allow users the flexibility of implementing their own transports + and requires grpc-go to maintain many separate transports. + +- Require byte-based transports. + + This does not allow for a more efficient in-memory transport that is capable + of copying proto messages rather than serializing and deserializing. + +- Inject the client `TransportBuilder` directly into the `ClientConn`, rather + than allowing the resolver and balancer to influence it. + + This would have a simpler API, however, it would require each `ClientConn` to + use only a single transport. The proposed design enables a `ClientConn` with + different types of connections. + +- Configure transports per-`ClientConn` instead of globally. + + The proposed approach matches how we handle balancers and resolvers. In + addition, the current design does not preclude the ability to support + per-`ClientConn` transport configuration if a need is encountered. + +## Implementation + +The client-side implementation will be completed first, with the server-side +implementation being made in a subsequent change. The existing HTTP/2 transport +will be refactored to support the new API, while the old, internal API will be +eliminated. All existing external APIs will remain unchanged, except as noted +above, but will be implemented using the new API. diff --git a/L37_graphics/client_diagram.png b/L37_graphics/client_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..891a36c0ca002aed11bec865d1813ecfc74ae6ef GIT binary patch literal 56838 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 literal 0 HcmV?d00001 diff --git a/L37_graphics/client_diagram.svg b/L37_graphics/client_diagram.svg new file mode 100644 index 000000000..ca4192037 --- /dev/null +++ b/L37_graphics/client_diagram.svg @@ -0,0 +1,212 @@ + + + + + + +%44115 + + + +ClientConn + +ClientConn + + + +resolver + +resolver + + + +ClientConn->resolver + + +  target + + + +balancer + +balancer + + + +ClientConn->balancer + + +  addresses + + + +ClientTransport + +ClientTransport + + + +ClientConn->ClientTransport + + +  headers + + + +ClientConn->ClientTransport + + +  Write + + + +Stream + +Stream + + + +ClientConn->Stream + + + +  Read + + + +resolver->ClientConn + + +  addresses + + + +n1 + +NewSubConn + + + +balancer->n1 + + +  addresses + + + +n2 + +NewSubConn + + + +balancer->n2 + + +  addresses + + + +NewClientTransport + +NewClientTransport + + + +n1->NewClientTransport + + +  address + + + +NewClientTransport->ClientConn + + +  ClientTransport + + + +Dialer + +Dialer + + + +NewClientTransport->Dialer + + +  address + + + +Handshaker + +Handshaker + + + +NewClientTransport->Handshaker + + +  Conn + + + +Dialer->NewClientTransport + + +  Conn + + + +Handshaker->NewClientTransport + + +  Conn + + + +ClientTransport->ClientConn + + +  Stream + + + From 9b5e98916beeb8955264b28c54b8ffbdaa1d12fa Mon Sep 17 00:00:00 2001 From: Doug Fawley Date: Thu, 20 Sep 2018 15:55:00 -0700 Subject: [PATCH 2/3] add discussion link --- L37-go-custom-transports.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/L37-go-custom-transports.md b/L37-go-custom-transports.md index b3178464d..10a55226c 100644 --- a/L37-go-custom-transports.md +++ b/L37-go-custom-transports.md @@ -5,7 +5,7 @@ L37: Go Custom Transports * Status: Draft * Implemented in: Go * Last updated: 2018-09-20 -* Discussion at: +* Discussion at: https://groups.google.com/forum/#!topic/grpc-io/TIGVcfQ_Ipg ## Abstract From bae82be173680e1bdd7d2650c2bd481e6e55bf3d Mon Sep 17 00:00:00 2001 From: Doug Fawley Date: Tue, 25 Oct 2022 14:13:15 -0700 Subject: [PATCH 3/3] remove half-connected concept which is no longer relevant --- L37-go-custom-transports.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/L37-go-custom-transports.md b/L37-go-custom-transports.md index 10a55226c..80ac780de 100644 --- a/L37-go-custom-transports.md +++ b/L37-go-custom-transports.md @@ -166,7 +166,7 @@ package client // TransportBuilder constructs Transports connected to addresses. type TransportBuilder interface { - // Build begins connecting to the address. It must return a Transport that + // Build connects to the address provided. It must return a Transport that // is ready to accept new streams or an error. Build(context.Context, resolver.Address, TransportMonitor, TransportBuildOptions) (Transport, error) } @@ -180,11 +180,6 @@ type TransportBuildOptions struct { // A TransportMonitor is a monitor for client-side transports. type TransportMonitor interface { - // Connected reports that the Transport is fully connected - i.e. the - // remote server has confirmed it is a gRPC server. - // - // May only be called once. - Connected() // OnError reports that the Transport has closed due to the error provided. // Existing streams may or may not continue. // @@ -192,10 +187,7 @@ type TransportMonitor interface { OnError(error) } -// A Transport is a client-side gRPC transport. It begins in a -// "half-connected" state where the client may opportunistically start new -// streams by calling NewStream. Some clients will wait until the -// TransportMonitor's Connected method is called. +// A Transport is a client-side gRPC transport. type Transport interface { // NewStream begins a new Stream on the Transport. Blocks until sufficient // stream quota is available, if applicable. If the Transport is closed,