@@ -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 ) {
@@ -146,48 +87,74 @@ func (p *connPool) Get(ctx context.Context, network, address string, opt remote.
14687 }
14788
14889 var (
149- trans * transports
150- conn * clientConn
151- err error
90+ tr grpc.ClientTransport
91+ idx uint32
92+ conn * clientConn
93+ err error
15294 )
15395
96+ // there is no need to check whether connPool has been closed
97+ // because connPool would only be closed when Kitex Client is GCed
15498 v , ok := p .conns .Load (address )
15599 if ok {
156- 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 ())
100+ trans := v .(* transports )
101+ tr , idx = trans .getActiveTransport ()
102+ if tr != nil {
103+ // Actually new a stream, reuse the connection (grpc.ClientTransport)
104+ conn , err = newClientConn (ctx , tr , address )
105+ if err == nil {
106+ return conn , nil
107+ }
108+
109+ // when stream creations failed:
110+ // - gRPC Connection closed or draining: create a new connection
111+ // - ctx canceled: the request lifecycle has ended, exit immediately
112+ select {
113+ // ctx provided by users is canceled, we should not try to create a new gRPC connection
114+ case <- ctx .Done ():
115+ return nil , err
116+ default :
165117 }
118+ klog .CtxDebugf (ctx , "KITEX: New grpc stream failed, network=%s, address=%s, error=%s" , network , address , err .Error ())
166119 }
167120 }
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
121+ rawTr , dErr , _ := p .sfg .Do (address , func () (i interface {}, e error ) {
122+ var trans * transports
123+ var isNew bool
124+ // avoid creating duplicate transports
125+ if existTrans , ok := p .conns .Load (address ); ok {
126+ trans = existTrans .(* transports )
127+ } else {
128+ trans = newTransports (p .size )
129+ isNew = true
174130 }
175- if trans == nil {
176- trans = & transports {
177- size : p .size ,
178- cliTransports : make ([]grpc.ClientTransport , p .size ),
131+
132+ res , cErr := trans .createTransport (idx , p .remoteService , opt .Dialer , network , address , opt .ConnectTimeout , p .connOpts )
133+ if cErr != nil {
134+ return nil , cErr
135+ }
136+
137+ if isNew {
138+ // Store first, then recheck closed state to eliminate TOCTOU:
139+ // if Close() finished its Range between our earlier check and this store,
140+ // self-clean here to prevent orphaned transports.
141+ p .conns .LoadOrStore (address , trans )
142+ if p .isClosed () {
143+ if recheckV , recheckOK := p .conns .LoadAndDelete (address ); recheckOK {
144+ recheckV .(* transports ).close ()
145+ }
146+ return nil , errConnPoolClosed
179147 }
180148 }
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
149+
150+ return res , nil
184151 })
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
152+ if dErr != nil {
153+ klog .CtxErrorf (ctx , "KITEX: New grpc client connection failed, network=%s, address=%s, error=%s" , network , address , dErr .Error ())
154+ return nil , dErr
188155 }
189156 klog .CtxDebugf (ctx , "KITEX: New grpc client connection succeed, network=%s, address=%s" , network , address )
190- return newClientConn (ctx , tr .(grpc.ClientTransport ), address )
157+ return newClientConn (ctx , rawTr .(grpc.ClientTransport ), address )
191158}
192159
193160// Put implements the ConnPool interface.
@@ -205,9 +172,7 @@ func (p *connPool) release(conn net.Conn) error {
205172}
206173
207174func (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 )
175+ tr , err := newTransport (p .remoteService , opt .Dialer , network , address , opt .ConnectTimeout , p .connOpts , nil , nil )
211176 if err != nil {
212177 return nil , err
213178 }
@@ -224,14 +189,17 @@ func (p *connPool) Discard(conn net.Conn) error {
224189
225190// Clean implements the LongConnPool interface.
226191func (p * connPool ) Clean (network , address string ) {
227- if v , ok := p .conns .Load (address ); ok {
228- p .conns .Delete (address )
192+ if v , ok := p .conns .LoadAndDelete (address ); ok {
229193 v .(* transports ).close ()
230194 }
231195}
232196
233197// Close is to release resource of ConnPool, it is executed when client is closed.
234198func (p * connPool ) Close () error {
199+ if ! p .casClosed () {
200+ return nil
201+ }
202+
235203 p .conns .Range (func (addr , trans interface {}) bool {
236204 p .conns .Delete (addr )
237205 trans .(* transports ).close ()
@@ -240,6 +208,19 @@ func (p *connPool) Close() error {
240208 return nil
241209}
242210
211+ func (p * connPool ) isClosed () bool {
212+ return atomic .LoadInt32 (& p .closed ) == poolClosed
213+ }
214+
215+ func (p * connPool ) casClosed () bool {
216+ return atomic .CompareAndSwapInt32 (& p .closed , poolOpen , poolClosed )
217+ }
218+
219+ type dumpEntry struct {
220+ addr string
221+ tr grpc.ClientTransport
222+ }
223+
243224// Dump dumps the connection pool with the details of the underlying transport.
244225func (p * connPool ) Dump () interface {} {
245226 defer func () {
@@ -251,32 +232,70 @@ func (p *connPool) Dump() interface{} {
251232 // remoteAddress -> []clientTransport, where each clientTransport is a connection. Distinguish the connection via localAddress.
252233 // If mesh egress is not enabled, toAddr should be the address of the callee service.
253234 // Otherwise, toAddr will be the same, so you should check the remoteAddress in each stream, which is read from the header.
235+
236+ // sync.Map does not expose its length directly.
237+ // Since dump is a cold-path operation, performance is not a major concern here
254238 poolDump := make (map [string ]interface {}, p .size )
239+ var cliTransDumps []dumpEntry
255240 p .conns .Range (func (k , v interface {}) bool {
256241 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
242+ for _ , tr := range v .(* transports ).loadAll () {
243+ cliTransDumps = append (cliTransDumps , dumpEntry {addr : addr , tr : tr })
274244 }
275245 return true
276246 })
247+
248+ for _ , cliTransDump := range cliTransDumps {
249+ dumper , ok := cliTransDump .tr .(interface { Dump () interface {} })
250+ if ! ok {
251+ continue
252+ }
253+ var curr []interface {}
254+ if poolDump [cliTransDump .addr ] == nil {
255+ curr = make ([]interface {}, 0 )
256+ } else {
257+ curr = poolDump [cliTransDump .addr ].([]interface {})
258+ }
259+ curr = append (curr , dumper .Dump ())
260+ poolDump [cliTransDump .addr ] = curr
261+ }
277262 return poolDump
278263}
279264
265+ // newTransport creates a gRPC connection
266+ func newTransport (remoteService string ,
267+ dialer remote.Dialer , network , address string , connectTimeout time.Duration , opts grpc.ConnectOptions ,
268+ onGoAway func (context.Context , grpc.ClientTransport , grpc.GoAwayReason ),
269+ onClose func (context.Context , grpc.ClientTransport , error ),
270+ ) (grpc.ClientTransport , error ) {
271+ conn , err := dialer .DialTimeout (network , address , connectTimeout )
272+ if err != nil {
273+ return nil , err
274+ }
275+ if opts .TLSConfig != nil {
276+ tlsConn , tErr := newTLSConn (conn , opts .TLSConfig )
277+ if tErr != nil {
278+ // release tls handshake failed connection
279+ cErr := conn .Close ()
280+ if cErr != nil {
281+ klog .Warnf ("KITEX: Close TLS handshake failed connection, err: %v" , cErr )
282+ }
283+ return nil , tErr
284+ }
285+ conn = tlsConn
286+ }
287+ return grpc .NewClientTransportWithConfig (
288+ context .Background (), // gRPC connection does not need to be bound to a specific ctx
289+ conn ,
290+ opts ,
291+ grpc.ClientConfig {
292+ RemoteService : remoteService ,
293+ OnGoAway : onGoAway ,
294+ OnClose : onClose ,
295+ },
296+ )
297+ }
298+
280299// newTLSConn constructs a client-side TLS connection and performs handshake.
281300func newTLSConn (conn net.Conn , tlsCfg * tls.Config ) (net.Conn , error ) {
282301 tlsConn := tls .Client (conn , tlsCfg )
@@ -285,3 +304,12 @@ func newTLSConn(conn net.Conn, tlsCfg *tls.Config) (net.Conn, error) {
285304 }
286305 return tlsConn , nil
287306}
307+
308+ func checkActive (trans grpc.ClientTransport ) bool {
309+ if trans == nil {
310+ return false
311+ }
312+ // grpc.ClientTransport is implemented by *http2Client in pkg/remote/trans/nphttp2/grpc
313+ // it implements grpc.IsActive
314+ return trans .(grpc.IsActive ).IsActive ()
315+ }
0 commit comments