Skip to content

Specialize the unary RPC flow internally #609

@jhump

Description

@jhump

Currently, all RPCs flow through the same internal abstraction: duplexHTTPCall. This abstraction can accommodate all manner of calls, including full-duplex bidirectional calls, but it is overkill for unary RPC. Some of the machinery needed to support bidirectional streaming (synchronization mechanisms and an extra goroutine and io.Pipe per call) is pure overhead and unnecessary for simple, unary RPCs.

We can eliminate this overhead by providing a dedicated flow for unary RPCs via a new method on the protocolClient interface. Something like so:

// Invoke invokes a unary RPC. The given req is sent to the server and the reply
// is unmarshalled into resp. Returns the HTTP method used, response headers,
// trailers, and optional error.
Invoke(ctx context.Context, spec Spec, reqHdr http.Header, req, resp any) (method string hdr, tlr http.Header, error)

A very naive implementation of this new method might look like so (just to demonstrate what it conceptually does):

func (m *myClient) Invoke(ctx context.Context, spec Spec, reqHdr http.Header, req, resp any) (http.Header, http.Header, error) {
	conn := client.protocolClient.NewConn(ctx, spec, reqHdr)
	if err := conn.Send(req); err != nil && !errors.Is(err, io.EOF) {
		_ = conn.CloseRequest()
		_ = conn.CloseResponse()
		return nil, err
	}
	if err := conn.CloseRequest(); err != nil {
		_ = conn.CloseResponse()
		return nil, err
	}
	
	if err := conn.Receive(resp); err != nil {
		return nil, err
	}
	// Make sure there's no extra message and also
	// make sure we've received any trailers.
	if err := conn.Receive(resp); err == nil {
		return nil, NewError(CodeUnknown, errors.New("unary stream has multiple messages"))
	} else if err != nil && !errors.Is(err, io.EOF) {
		return nil, NewError(CodeUnknown, err)
	}
	return http.MethodPost, conn.ResponseHeader(), conn.ResponseTrailer(), conn.CloseResponse() 
}

But the value of it is that it enables a much more bespoke flow that eschews the under-the-hood complexity of the StreamingClientConn (which is implemented by way of duplexHTTPConn).

Having this in place would also make it trivial to resolve #541 for unary RPCs in a way that doesn't further complicate duplexHTTPConn.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions