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 }