go.uber.org/yarpc@v1.72.1/transport/tchannel/channel_transport.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  	"errors"
    25  
    26  	"github.com/opentracing/opentracing-go"
    27  	"github.com/uber/tchannel-go"
    28  	"go.uber.org/yarpc/api/transport"
    29  	"go.uber.org/yarpc/pkg/lifecycle"
    30  	"go.uber.org/zap"
    31  )
    32  
    33  var errChannelOrServiceNameIsRequired = errors.New(
    34  	"cannot instantiate tchannel.ChannelTransport: " +
    35  		"please provide a service name with the ServiceName option " +
    36  		"or an existing Channel with the WithChannel option")
    37  
    38  // NewChannelTransport is a YARPC transport that facilitates sending and
    39  // receiving YARPC requests through TChannel. It uses a shared TChannel
    40  // Channel for both, incoming and outgoing requests, ensuring reuse of
    41  // connections and other resources.
    42  //
    43  // Either the local service name (with the ServiceName option) or a user-owned
    44  // TChannel (with the WithChannel option) MUST be specified.
    45  //
    46  // ChannelTransport uses the underlying TChannel Channel for load balancing
    47  // and peer managament.
    48  // Use NewTransport and its NewOutbound to support YARPC peer.Choosers.
    49  func NewChannelTransport(opts ...TransportOption) (*ChannelTransport, error) {
    50  	var options transportOptions
    51  	options.tracer = opentracing.GlobalTracer()
    52  	for _, opt := range opts {
    53  		opt(&options)
    54  	}
    55  
    56  	// Attempt to construct a channel on behalf of the caller if none given.
    57  	// Defer the error until Start since NewChannelTransport does not have
    58  	// an error return.
    59  	var err error
    60  	ch := options.ch
    61  
    62  	if ch == nil {
    63  		if options.name == "" {
    64  			err = errChannelOrServiceNameIsRequired
    65  		} else {
    66  			opts := tchannel.ChannelOptions{Tracer: options.tracer}
    67  			ch, err = tchannel.NewChannel(options.name, &opts)
    68  			options.ch = ch
    69  		}
    70  	}
    71  
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	return options.newChannelTransport(), nil
    77  }
    78  
    79  func (options transportOptions) newChannelTransport() *ChannelTransport {
    80  	logger := options.logger
    81  	if logger == nil {
    82  		logger = zap.NewNop()
    83  	}
    84  	return &ChannelTransport{
    85  		once:              lifecycle.NewOnce(),
    86  		ch:                options.ch,
    87  		addr:              options.addr,
    88  		tracer:            options.tracer,
    89  		logger:            logger.Named("tchannel"),
    90  		originalHeaders:   options.originalHeaders,
    91  		newResponseWriter: newHandlerWriter,
    92  	}
    93  }
    94  
    95  // ChannelTransport maintains TChannel peers and creates inbounds and outbounds for
    96  // TChannel.
    97  // If you have a YARPC peer.Chooser, use the unqualified tchannel.Transport
    98  // instead.
    99  type ChannelTransport struct {
   100  	once              *lifecycle.Once
   101  	ch                Channel
   102  	addr              string
   103  	tracer            opentracing.Tracer
   104  	logger            *zap.Logger
   105  	router            transport.Router
   106  	originalHeaders   bool
   107  	newResponseWriter func(inboundCallResponse, tchannel.Format, headerCase) responseWriter
   108  }
   109  
   110  // Channel returns the underlying TChannel "Channel" instance.
   111  func (t *ChannelTransport) Channel() Channel {
   112  	return t.ch
   113  }
   114  
   115  // ListenAddr exposes the listen address of the transport.
   116  func (t *ChannelTransport) ListenAddr() string {
   117  	return t.addr
   118  }
   119  
   120  // Start starts the TChannel transport. This starts making connections and
   121  // accepting inbound requests. All inbounds must have been assigned a router
   122  // to accept inbound requests before this is called.
   123  func (t *ChannelTransport) Start() error {
   124  	return t.once.Start(t.start)
   125  }
   126  
   127  func (t *ChannelTransport) start() error {
   128  
   129  	if t.router != nil {
   130  		// Set up handlers. This must occur after construction because the
   131  		// dispatcher, or its equivalent, calls SetRouter before Start.
   132  		// This also means that SetRouter should be called on every inbound
   133  		// before calling Start on any transport or inbound.
   134  		services := make(map[string]struct{})
   135  		for _, p := range t.router.Procedures() {
   136  			services[p.Service] = struct{}{}
   137  		}
   138  
   139  		for s := range services {
   140  			sc := t.ch.GetSubChannel(s)
   141  			existing := sc.GetHandlers()
   142  			sc.SetHandler(handler{existing: existing, router: t.router, tracer: t.tracer, logger: t.logger, newResponseWriter: t.newResponseWriter})
   143  		}
   144  	}
   145  
   146  	if t.ch.State() == tchannel.ChannelListening {
   147  		// Channel.Start() was called before RPC.Start(). We still want to
   148  		// update the Handler and what t.addr means, but nothing else.
   149  		t.addr = t.ch.PeerInfo().HostPort
   150  		return nil
   151  	}
   152  
   153  	// Default to ListenIP if addr wasn't given.
   154  	addr := t.addr
   155  	if addr == "" {
   156  		listenIP, err := tchannel.ListenIP()
   157  		if err != nil {
   158  			return err
   159  		}
   160  
   161  		addr = listenIP.String() + ":0"
   162  		// TODO(abg): Find a way to export this to users
   163  	}
   164  
   165  	// TODO(abg): If addr was just the port (":4040"), we want to use
   166  	// ListenIP() + ":4040" rather than just ":4040".
   167  
   168  	if err := t.ch.ListenAndServe(addr); err != nil {
   169  		return err
   170  	}
   171  
   172  	t.addr = t.ch.PeerInfo().HostPort
   173  	return nil
   174  }
   175  
   176  // Stop stops the TChannel transport. It starts rejecting incoming requests
   177  // and draining connections before closing them.
   178  // In a future version of YARPC, Stop will block until the underlying channel
   179  // has closed completely.
   180  func (t *ChannelTransport) Stop() error {
   181  	return t.once.Stop(t.stop)
   182  }
   183  
   184  func (t *ChannelTransport) stop() error {
   185  	t.ch.Close()
   186  	return nil
   187  }
   188  
   189  // IsRunning returns whether the ChannelTransport is running.
   190  func (t *ChannelTransport) IsRunning() bool {
   191  	return t.once.IsRunning()
   192  }