go.uber.org/yarpc@v1.72.1/transport/tchannel/outbound.go (about)

     1  // Copyright (c) 2022 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package tchannel
    22  
    23  import (
    24  	"bytes"
    25  	"context"
    26  	"io"
    27  	"strconv"
    28  
    29  	"github.com/uber/tchannel-go"
    30  	"go.uber.org/yarpc/api/peer"
    31  	"go.uber.org/yarpc/api/transport"
    32  	"go.uber.org/yarpc/api/x/introspection"
    33  	"go.uber.org/yarpc/internal/bufferpool"
    34  	"go.uber.org/yarpc/internal/iopool"
    35  	intyarpcerrors "go.uber.org/yarpc/internal/yarpcerrors"
    36  	peerchooser "go.uber.org/yarpc/peer"
    37  	"go.uber.org/yarpc/peer/hostport"
    38  	"go.uber.org/yarpc/pkg/errors"
    39  	"go.uber.org/yarpc/pkg/lifecycle"
    40  	"go.uber.org/yarpc/yarpcerrors"
    41  )
    42  
    43  var (
    44  	errDoNotUseContextWithHeaders = yarpcerrors.Newf(yarpcerrors.CodeInvalidArgument, "tchannel.ContextWithHeaders is not compatible with YARPC, use yarpc.CallOption instead")
    45  
    46  	_ transport.UnaryOutbound              = (*Outbound)(nil)
    47  	_ introspection.IntrospectableOutbound = (*Outbound)(nil)
    48  )
    49  
    50  // Outbound sends YARPC requests over TChannel.
    51  // It may be constructed using the NewOutbound or NewSingleOutbound methods on
    52  // the TChannel Transport.
    53  type Outbound struct {
    54  	transport   *Transport
    55  	chooser     peer.Chooser
    56  	once        *lifecycle.Once
    57  	reuseBuffer bool
    58  }
    59  
    60  // OutboundOption customizes the behavior of a TChannel Outbound.
    61  type OutboundOption func(o *Outbound)
    62  
    63  // WithReuseBuffer configures the Outbound to
    64  // use a buffer pool to read the response bytes.
    65  func WithReuseBuffer(enable bool) OutboundOption {
    66  	return func(o *Outbound) {
    67  		o.reuseBuffer = enable
    68  	}
    69  }
    70  
    71  // NewOutbound builds a new TChannel outbound that selects a peer for each
    72  // request using the given peer chooser.
    73  func (t *Transport) NewOutbound(chooser peer.Chooser, opts ...OutboundOption) *Outbound {
    74  	o := &Outbound{
    75  		once:      lifecycle.NewOnce(),
    76  		transport: t,
    77  		chooser:   chooser,
    78  	}
    79  	for _, opt := range opts {
    80  		opt(o)
    81  	}
    82  	return o
    83  }
    84  
    85  // NewSingleOutbound builds a new TChannel outbound always using the peer with
    86  // the given address.
    87  func (t *Transport) NewSingleOutbound(addr string, opts ...OutboundOption) *Outbound {
    88  	chooser := peerchooser.NewSingle(hostport.PeerIdentifier(addr), t)
    89  	return t.NewOutbound(chooser, opts...)
    90  }
    91  
    92  // TransportName is the transport name that will be set on `transport.Request` struct.
    93  func (o *Outbound) TransportName() string {
    94  	return TransportName
    95  }
    96  
    97  // Chooser returns the outbound's peer chooser.
    98  func (o *Outbound) Chooser() peer.Chooser {
    99  	return o.chooser
   100  }
   101  
   102  // Call sends an RPC over this TChannel outbound.
   103  func (o *Outbound) Call(ctx context.Context, req *transport.Request) (*transport.Response, error) {
   104  	if req == nil {
   105  		return nil, yarpcerrors.InvalidArgumentErrorf("request for tchannel outbound was nil")
   106  	}
   107  	if err := o.once.WaitUntilRunning(ctx); err != nil {
   108  		return nil, intyarpcerrors.AnnotateWithInfo(yarpcerrors.FromError(err), "error waiting for tchannel outbound to start for service: %s", req.Service)
   109  	}
   110  	if _, ok := ctx.(tchannel.ContextWithHeaders); ok {
   111  		return nil, errDoNotUseContextWithHeaders
   112  	}
   113  	p, onFinish, err := o.getPeerForRequest(ctx, req)
   114  	if err != nil {
   115  		return nil, toYARPCError(req, err)
   116  	}
   117  	res, err := p.Call(ctx, req, o.reuseBuffer)
   118  	onFinish(err)
   119  	return res, toYARPCError(req, err)
   120  }
   121  
   122  // Call sends an RPC to this specific peer.
   123  func (p *tchannelPeer) Call(ctx context.Context, req *transport.Request, reuseBuffer bool) (*transport.Response, error) {
   124  	return callWithPeer(ctx, req, p.getPeer(), p.transport.headerCase, reuseBuffer)
   125  }
   126  
   127  // callWithPeer sends a request with the chosen peer.
   128  func callWithPeer(ctx context.Context, req *transport.Request, peer *tchannel.Peer, headerCase headerCase, reuseBuffer bool) (*transport.Response, error) {
   129  	// NB(abg): Under the current API, the local service's name is required
   130  	// twice: once when constructing the TChannel and then again when
   131  	// constructing the RPC.
   132  	var call *tchannel.OutboundCall
   133  	var err error
   134  
   135  	format := tchannel.Format(req.Encoding)
   136  	callOptions := tchannel.CallOptions{
   137  		Format:          format,
   138  		CallerName:      req.Caller,
   139  		ShardKey:        req.ShardKey,
   140  		RoutingKey:      req.RoutingKey,
   141  		RoutingDelegate: req.RoutingDelegate,
   142  	}
   143  
   144  	// If the hostport is given, we use the BeginCall on the channel
   145  	// instead of the subchannel.
   146  	call, err = peer.BeginCall(
   147  		// TODO(abg): Set TimeoutPerAttempt in the context's retry options if
   148  		// TTL is set.
   149  		// (kris): Consider instead moving TimeoutPerAttempt to an outer
   150  		// layer, just clamp the context on outbound call.
   151  		ctx,
   152  		req.Service,
   153  		req.Procedure,
   154  		&callOptions,
   155  	)
   156  
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  	reqHeaders := headerMap(req.Headers, headerCase)
   161  
   162  	// for tchannel, callerProcedure is added to application headers.
   163  	reqHeaders = requestCallerProcedureToHeader(req, reqHeaders)
   164  
   165  	// baggage headers are transport implementation details that are stripped out (and stored in the context). Users don't interact with it
   166  	tracingBaggage := tchannel.InjectOutboundSpan(call.Response(), nil)
   167  	if err := writeHeaders(format, reqHeaders, tracingBaggage, call.Arg2Writer); err != nil {
   168  		// TODO(abg): This will wrap IO errors while writing headers as encode
   169  		// errors. We should fix that.
   170  		return nil, errors.RequestHeadersEncodeError(req, err)
   171  	}
   172  
   173  	if err := writeBody(req.Body, call); err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	res := call.Response()
   178  	headers, err := readHeaders(format, res.Arg2Reader)
   179  	if err != nil {
   180  		if err, ok := err.(tchannel.SystemError); ok {
   181  			return nil, fromSystemError(err)
   182  		}
   183  		// TODO(abg): This will wrap IO errors while reading headers as decode
   184  		// errors. We should fix that.
   185  		return nil, errors.ResponseHeadersDecodeError(req, err)
   186  	}
   187  
   188  	resBody, err := res.Arg3Reader()
   189  	if err != nil {
   190  		if err, ok := err.(tchannel.SystemError); ok {
   191  			return nil, fromSystemError(err)
   192  		}
   193  		return nil, err
   194  	}
   195  
   196  	body, bodySize, err := getResponseBody(resBody, reuseBuffer)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  
   201  	if err = resBody.Close(); err != nil {
   202  		return nil, err
   203  	}
   204  
   205  	respService, _ := headers.Get(ServiceHeaderKey) // validateServiceName handles empty strings
   206  	if err := validateServiceName(req.Service, respService); err != nil {
   207  		return nil, err
   208  	}
   209  
   210  	applicationErrorName, _ := headers.Get(ApplicationErrorNameHeaderKey)
   211  	applicationErrorCode := getApplicationErrorCodeFromHeaders(headers)
   212  	applicationErrorDetails, _ := headers.Get(ApplicationErrorDetailsHeaderKey)
   213  
   214  	err = getResponseError(headers)
   215  	deleteReservedHeaders(headers)
   216  
   217  	resp := &transport.Response{
   218  		Headers:          headers,
   219  		Body:             body,
   220  		BodySize:         bodySize,
   221  		ApplicationError: res.ApplicationError(),
   222  		ApplicationErrorMeta: &transport.ApplicationErrorMeta{
   223  			Details: applicationErrorDetails,
   224  			Name:    applicationErrorName,
   225  			Code:    applicationErrorCode,
   226  		},
   227  	}
   228  	return resp, err
   229  }
   230  
   231  func getResponseBody(resBody tchannel.ArgReader, reuseBuffer bool) (body io.ReadCloser, bodySize int, err error) {
   232  	if reuseBuffer {
   233  		buffer := bufferpool.NewAutoReleaseBuffer()
   234  		if _, err = buffer.ReadFrom(resBody); err != nil {
   235  			return nil, 0, err
   236  		}
   237  		body = readerCloser{
   238  			Reader: bytes.NewReader(buffer.Bytes()),
   239  			Closer: buffer,
   240  		}
   241  		return body, buffer.Len(), nil
   242  	}
   243  	buffer := bytes.NewBuffer(make([]byte, 0, _defaultBufferSize))
   244  	if _, err = buffer.ReadFrom(resBody); err != nil {
   245  		return nil, 0, err
   246  	}
   247  
   248  	body = io.NopCloser(bytes.NewReader(buffer.Bytes()))
   249  	return body, buffer.Len(), nil
   250  }
   251  
   252  func writeBody(body io.Reader, call *tchannel.OutboundCall) error {
   253  	w, err := call.Arg3Writer()
   254  	if err != nil {
   255  		return err
   256  	}
   257  
   258  	if _, err := iopool.Copy(w, body); err != nil {
   259  		return err
   260  	}
   261  
   262  	return w.Close()
   263  }
   264  
   265  func getResponseError(headers transport.Headers) error {
   266  	errorCodeString, ok := headers.Get(ErrorCodeHeaderKey)
   267  	if !ok {
   268  		return nil
   269  	}
   270  	var errorCode yarpcerrors.Code
   271  	if err := errorCode.UnmarshalText([]byte(errorCodeString)); err != nil {
   272  		return err
   273  	}
   274  	if errorCode == yarpcerrors.CodeOK {
   275  		return yarpcerrors.Newf(yarpcerrors.CodeInternal, "got CodeOK from error header")
   276  	}
   277  	errorName, _ := headers.Get(ErrorNameHeaderKey)
   278  	errorMessage, _ := headers.Get(ErrorMessageHeaderKey)
   279  	return intyarpcerrors.NewWithNamef(errorCode, errorName, errorMessage)
   280  }
   281  
   282  func getApplicationErrorCodeFromHeaders(headers transport.Headers) *yarpcerrors.Code {
   283  	errorCodeHeader, found := headers.Get(ApplicationErrorCodeHeaderKey)
   284  	if !found {
   285  		return nil
   286  	}
   287  
   288  	errorCode, err := strconv.Atoi(errorCodeHeader)
   289  	if err != nil {
   290  		return nil
   291  	}
   292  
   293  	yarpcCode := yarpcerrors.Code(errorCode)
   294  	return &yarpcCode
   295  }
   296  
   297  func (o *Outbound) getPeerForRequest(ctx context.Context, treq *transport.Request) (*tchannelPeer, func(error), error) {
   298  	p, onFinish, err := o.chooser.Choose(ctx, treq)
   299  	if err != nil {
   300  		return nil, nil, err
   301  	}
   302  
   303  	tp, ok := p.(*tchannelPeer)
   304  	if !ok {
   305  		return nil, nil, peer.ErrInvalidPeerConversion{
   306  			Peer:         p,
   307  			ExpectedType: "*tchannelPeer",
   308  		}
   309  	}
   310  
   311  	return tp, onFinish, nil
   312  }
   313  
   314  // Transports returns the underlying TChannel Transport for this outbound.
   315  func (o *Outbound) Transports() []transport.Transport {
   316  	return []transport.Transport{o.transport}
   317  }
   318  
   319  // Start starts the TChannel outbound.
   320  func (o *Outbound) Start() error {
   321  	return o.once.Start(o.chooser.Start)
   322  }
   323  
   324  // Stop stops the TChannel outbound.
   325  func (o *Outbound) Stop() error {
   326  	return o.once.Stop(o.chooser.Stop)
   327  }
   328  
   329  // IsRunning returns whether the ChannelOutbound is running.
   330  func (o *Outbound) IsRunning() bool {
   331  	return o.once.IsRunning()
   332  }
   333  
   334  // Introspect returns basic status about this outbound.
   335  func (o *Outbound) Introspect() introspection.OutboundStatus {
   336  	state := "Stopped"
   337  	if o.IsRunning() {
   338  		state = "Running"
   339  	}
   340  	var chooser introspection.ChooserStatus
   341  	if i, ok := o.chooser.(introspection.IntrospectableChooser); ok {
   342  		chooser = i.Introspect()
   343  	} else {
   344  		chooser = introspection.ChooserStatus{
   345  			Name: "Introspection not available",
   346  		}
   347  	}
   348  	return introspection.OutboundStatus{
   349  		Transport: "tchannel",
   350  		State:     state,
   351  		Chooser:   chooser,
   352  	}
   353  }
   354  
   355  type readerCloser struct {
   356  	*bytes.Reader
   357  	io.Closer
   358  }