@@ -30,11 +30,16 @@ import (
3030
3131 "github.com/cloudwego/kitex/pkg/klog"
3232 "github.com/cloudwego/kitex/pkg/remote"
33+ "github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/codes"
3334 "github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/grpc"
35+ "github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/status"
3436 "github.com/cloudwego/kitex/pkg/rpcinfo"
3537)
3638
37- var _ remote.LongConnPool = & connPool {}
39+ const (
40+ poolOpen int32 = 0
41+ poolClosed int32 = 1
42+ )
3843
3944func poolSize () uint32 {
4045 // One connection per processor, and need redundancy。
@@ -60,84 +65,20 @@ func NewConnPool(remoteService string, size uint32, connOpts grpc.ConnectOptions
6065 }
6166}
6267
63- // MuxPool manages a pool of long connections.
68+ // connPool manages a pool of gRPC long connections.
6469type connPool struct {
6570 size uint32
6671 sfg singleflight.Group
6772 conns sync.Map // key: address, value: *transports
6873 remoteService string // remote service name
6974 connOpts grpc.ConnectOptions
75+ closed int32 // 1 means connPool has been closed
7076}
7177
72- type transports struct {
73- index uint32
74- size uint32
75- cliTransports []grpc.ClientTransport
76- }
77-
78- // get connection from the pool, load balance with round-robin.
79- func (t * transports ) get () grpc.ClientTransport {
80- idx := atomic .AddUint32 (& t .index , 1 )
81- return t .cliTransports [idx % t .size ]
82- }
83-
84- // put find the first empty position to put the connection to the pool.
85- func (t * transports ) put (trans grpc.ClientTransport ) {
86- for i := 0 ; i < int (t .size ); i ++ {
87- cliTransport := t .cliTransports [i ]
88- if cliTransport == nil {
89- t .cliTransports [i ] = trans
90- return
91- }
92- if ! cliTransport .(grpc.IsActive ).IsActive () {
93- t .cliTransports [i ].GracefulClose ()
94- t .cliTransports [i ] = trans
95- return
96- }
97- }
98- }
99-
100- // close all connections of the pool.
101- func (t * transports ) close () {
102- for i := range t .cliTransports {
103- if c := t .cliTransports [i ]; c != nil {
104- c .GracefulClose ()
105- }
106- }
107- }
108-
109- var _ remote.LongConnPool = (* connPool )(nil )
110-
111- func (p * connPool ) newTransport (ctx context.Context , dialer remote.Dialer , network , address string ,
112- connectTimeout time.Duration , opts grpc.ConnectOptions ,
113- ) (grpc.ClientTransport , error ) {
114- conn , err := dialer .DialTimeout (network , address , connectTimeout )
115- if err != nil {
116- return nil , err
117- }
118- if opts .TLSConfig != nil {
119- tlsConn , err := newTLSConn (conn , opts .TLSConfig )
120- if err != nil {
121- return nil , err
122- }
123- conn = tlsConn
124- }
125- return grpc .NewClientTransport (
126- ctx ,
127- conn ,
128- opts ,
129- p .remoteService ,
130- func (grpc.GoAwayReason ) {
131- // remove connection from the pool.
132- // we do not need to close this grpc transport manually
133- // since grpc client is responsible for doing this.
134- p .conns .Delete (address )
135- },
136- func () {
137- // do nothing
138- },
139- )
140- }
78+ var (
79+ _ remote.LongConnPool = (* connPool )(nil )
80+ errConnPoolClosed = status .Err (codes .Aborted , "connection pool has been closed" )
81+ )
14182
14283// Get pick or generate a net.Conn and return
14384func (p * connPool ) Get (ctx context.Context , network , address string , opt remote.ConnOption ) (net.Conn , error ) {
@@ -147,47 +88,60 @@ func (p *connPool) Get(ctx context.Context, network, address string, opt remote.
14788
14889 var (
14990 trans * transports
91+ tr grpc.ClientTransport
92+ idx uint32
15093 conn * clientConn
15194 err error
15295 )
15396
97+ // there is no need to check whether connPool has been closed
98+ // because connPool would only be closed when Kitex Client is GCed
15499 v , ok := p .conns .Load (address )
155100 if ok {
156101 trans = v .(* transports )
157- if tr := trans .get (); tr != nil {
158- if tr .(grpc.IsActive ).IsActive () {
159- // Actually new a stream, reuse the connection (grpc.ClientTransport)
160- conn , err = newClientConn (ctx , tr , address )
161- if err == nil {
162- return conn , nil
163- }
164- klog .CtxDebugf (ctx , "KITEX: New grpc stream failed, network=%s, address=%s, error=%s" , network , address , err .Error ())
102+ tr , idx = trans .getActiveTransport ()
103+ if tr != nil {
104+ // Actually new a stream, reuse the connection (grpc.ClientTransport)
105+ conn , err = newClientConn (ctx , tr , address )
106+ if err == nil {
107+ return conn , nil
165108 }
109+ klog .CtxDebugf (ctx , "KITEX: New grpc stream failed, network=%s, address=%s, error=%s" , network , address , err .Error ())
166110 }
167111 }
168- tr , err , _ := p .sfg .Do (address , func () (i interface {}, e error ) {
169- // Notice: newTransport means new a connection, the timeout of connection cannot be set,
170- // so using context.Background() but not the ctx passed in as the parameter.
171- tr , err := p .newTransport (context .Background (), opt .Dialer , network , address , opt .ConnectTimeout , p .connOpts )
172- if err != nil {
173- return nil , err
112+ rawTr , dErr , _ := p .sfg .Do (address , func () (i interface {}, e error ) {
113+ // avoid creating duplicate transports
114+ var isNew bool
115+ if existTrans , ok := p .conns .Load (address ); ok {
116+ trans = existTrans .(* transports )
117+ } else {
118+ trans = newTransports (p .size )
119+ isNew = true
174120 }
175- if trans == nil {
176- trans = & transports {
177- size : p .size ,
178- cliTransports : make ([]grpc.ClientTransport , p .size ),
121+
122+ res , cErr := trans .createTransport (idx , p .remoteService , opt .Dialer , network , address , opt .ConnectTimeout , p .connOpts )
123+ if cErr != nil {
124+ return nil , cErr
125+ }
126+
127+ if isNew {
128+ if ! p .isClosed () {
129+ p .conns .LoadOrStore (address , trans )
130+ } else {
131+ // Concurrent Get() and Close(): avoid leaking newly created gRPC connection after Close().
132+ trans .close ()
133+ return nil , errConnPoolClosed
179134 }
180135 }
181- trans .put (tr ) // the tr (connection) maybe not in the pool, but can be recycled by keepalive.
182- p .conns .Store (address , trans )
183- return tr , nil
136+
137+ return res , nil
184138 })
185- if err != nil {
186- klog .CtxErrorf (ctx , "KITEX: New grpc client connection failed, network=%s, address=%s, error=%s" , network , address , err .Error ())
187- return nil , err
139+ if dErr != nil {
140+ klog .CtxErrorf (ctx , "KITEX: New grpc client connection failed, network=%s, address=%s, error=%s" , network , address , dErr .Error ())
141+ return nil , dErr
188142 }
189143 klog .CtxDebugf (ctx , "KITEX: New grpc client connection succeed, network=%s, address=%s" , network , address )
190- return newClientConn (ctx , tr .(grpc.ClientTransport ), address )
144+ return newClientConn (ctx , rawTr .(grpc.ClientTransport ), address )
191145}
192146
193147// Put implements the ConnPool interface.
@@ -205,9 +159,7 @@ func (p *connPool) release(conn net.Conn) error {
205159}
206160
207161func (p * connPool ) createShortConn (ctx context.Context , network , address string , opt remote.ConnOption ) (net.Conn , error ) {
208- // Notice: newTransport means new a connection, the timeout of connection cannot be set,
209- // so using context.Background() but not the ctx passed in as the parameter.
210- tr , err := p .newTransport (context .Background (), opt .Dialer , network , address , opt .ConnectTimeout , p .connOpts )
162+ tr , err := newTransport (p .remoteService , opt .Dialer , network , address , opt .ConnectTimeout , p .connOpts , nil , nil )
211163 if err != nil {
212164 return nil , err
213165 }
@@ -224,14 +176,17 @@ func (p *connPool) Discard(conn net.Conn) error {
224176
225177// Clean implements the LongConnPool interface.
226178func (p * connPool ) Clean (network , address string ) {
227- if v , ok := p .conns .Load (address ); ok {
228- p .conns .Delete (address )
179+ if v , ok := p .conns .LoadAndDelete (address ); ok {
229180 v .(* transports ).close ()
230181 }
231182}
232183
233184// Close is to release resource of ConnPool, it is executed when client is closed.
234185func (p * connPool ) Close () error {
186+ if ! p .casClosed () {
187+ return nil
188+ }
189+
235190 p .conns .Range (func (addr , trans interface {}) bool {
236191 p .conns .Delete (addr )
237192 trans .(* transports ).close ()
@@ -240,6 +195,19 @@ func (p *connPool) Close() error {
240195 return nil
241196}
242197
198+ func (p * connPool ) isClosed () bool {
199+ return atomic .LoadInt32 (& p .closed ) == poolClosed
200+ }
201+
202+ func (p * connPool ) casClosed () bool {
203+ return atomic .CompareAndSwapInt32 (& p .closed , poolOpen , poolClosed )
204+ }
205+
206+ type dumpEntry struct {
207+ addr string
208+ tr grpc.ClientTransport
209+ }
210+
243211// Dump dumps the connection pool with the details of the underlying transport.
244212func (p * connPool ) Dump () interface {} {
245213 defer func () {
@@ -251,32 +219,67 @@ func (p *connPool) Dump() interface{} {
251219 // remoteAddress -> []clientTransport, where each clientTransport is a connection. Distinguish the connection via localAddress.
252220 // If mesh egress is not enabled, toAddr should be the address of the callee service.
253221 // Otherwise, toAddr will be the same, so you should check the remoteAddress in each stream, which is read from the header.
222+
223+ // sync.Map does not expose its length directly.
224+ // Since dump is a cold-path operation, performance is not a major concern here
254225 poolDump := make (map [string ]interface {}, p .size )
226+ var cliTransDumps []dumpEntry
255227 p .conns .Range (func (k , v interface {}) bool {
256228 addr := k .(string )
257- ts := v .(* transports )
258- for _ , t := range ts .cliTransports {
259- if t == nil {
260- continue
261- }
262- dumper , ok := t .(interface { Dump () interface {} })
263- if ! ok {
264- continue
265- }
266- var curr []interface {}
267- if poolDump [addr ] == nil {
268- curr = make ([]interface {}, 0 )
269- } else {
270- curr = poolDump [addr ].([]interface {})
271- }
272- curr = append (curr , dumper .Dump ())
273- poolDump [addr ] = curr
229+ for _ , tr := range v .(* transports ).loadAll () {
230+ cliTransDumps = append (cliTransDumps , dumpEntry {addr : addr , tr : tr })
274231 }
275232 return true
276233 })
234+
235+ for _ , cliTransDump := range cliTransDumps {
236+ dumper , ok := cliTransDump .tr .(interface { Dump () interface {} })
237+ if ! ok {
238+ continue
239+ }
240+ var curr []interface {}
241+ if poolDump [cliTransDump .addr ] == nil {
242+ curr = make ([]interface {}, 0 )
243+ } else {
244+ curr = poolDump [cliTransDump .addr ].([]interface {})
245+ }
246+ curr = append (curr , dumper .Dump ())
247+ poolDump [cliTransDump .addr ] = curr
248+ }
277249 return poolDump
278250}
279251
252+ // newTransport creates a gRPC connection
253+ func newTransport (remoteService string ,
254+ dialer remote.Dialer , network , address string , connectTimeout time.Duration , opts grpc.ConnectOptions ,
255+ onGoAway func (context.Context , grpc.ClientTransport , grpc.GoAwayReason ),
256+ onClose func (context.Context , grpc.ClientTransport , error ),
257+ ) (grpc.ClientTransport , error ) {
258+ conn , err := dialer .DialTimeout (network , address , connectTimeout )
259+ if err != nil {
260+ return nil , err
261+ }
262+ if opts .TLSConfig != nil {
263+ tlsConn , tErr := newTLSConn (conn , opts .TLSConfig )
264+ if tErr != nil {
265+ // release tls handshake failed connection
266+ conn .Close ()
267+ return nil , tErr
268+ }
269+ conn = tlsConn
270+ }
271+ return grpc .NewClientTransportWithConfig (
272+ context .Background (), // gRPC connection does not need to be bound to a specific ctx
273+ conn ,
274+ opts ,
275+ grpc.ClientConfig {
276+ RemoteService : remoteService ,
277+ OnGoAway : onGoAway ,
278+ OnClose : onClose ,
279+ },
280+ )
281+ }
282+
280283// newTLSConn constructs a client-side TLS connection and performs handshake.
281284func newTLSConn (conn net.Conn , tlsCfg * tls.Config ) (net.Conn , error ) {
282285 tlsConn := tls .Client (conn , tlsCfg )
@@ -285,3 +288,12 @@ func newTLSConn(conn net.Conn, tlsCfg *tls.Config) (net.Conn, error) {
285288 }
286289 return tlsConn , nil
287290}
291+
292+ func checkActive (trans grpc.ClientTransport ) bool {
293+ if trans == nil {
294+ return false
295+ }
296+ // grpc.ClientTransport is implemented by *http2Client in pkg/remote/trans/nphttp2/grpc
297+ // it implements grpc.IsActive
298+ return trans .(grpc.IsActive ).IsActive ()
299+ }
0 commit comments