@@ -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,64 @@ 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
165107 }
108+ klog .CtxDebugf (ctx , "KITEX: New grpc stream failed, network=%s, address=%s, error=%s" , network , address , err .Error ())
166109 }
167110 }
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
111+ rawTr , dErr , _ := p .sfg .Do (address , func () (i interface {}, e error ) {
112+ var trans * transports
113+ var isNew bool
114+ // avoid creating duplicate transports
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+ // Store first, then recheck closed state to eliminate TOCTOU:
129+ // if Close() finished its Range between our earlier check and this store,
130+ // self-clean here to prevent orphaned transports.
131+ p .conns .LoadOrStore (address , trans )
132+ if p .isClosed () {
133+ if recheckV , recheckOK := p .conns .LoadAndDelete (address ); recheckOK {
134+ recheckV .(* transports ).close ()
135+ }
136+ return nil , errConnPoolClosed
179137 }
180138 }
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
139+
140+ return res , nil
184141 })
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
142+ if dErr != nil {
143+ klog .CtxErrorf (ctx , "KITEX: New grpc client connection failed, network=%s, address=%s, error=%s" , network , address , dErr .Error ())
144+ return nil , dErr
188145 }
189146 klog .CtxDebugf (ctx , "KITEX: New grpc client connection succeed, network=%s, address=%s" , network , address )
190- return newClientConn (ctx , tr .(grpc.ClientTransport ), address )
147+ return newClientConn (ctx , rawTr .(grpc.ClientTransport ), address )
191148}
192149
193150// Put implements the ConnPool interface.
@@ -205,9 +162,7 @@ func (p *connPool) release(conn net.Conn) error {
205162}
206163
207164func (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 )
165+ tr , err := newTransport (p .remoteService , opt .Dialer , network , address , opt .ConnectTimeout , p .connOpts , nil , nil )
211166 if err != nil {
212167 return nil , err
213168 }
@@ -224,14 +179,17 @@ func (p *connPool) Discard(conn net.Conn) error {
224179
225180// Clean implements the LongConnPool interface.
226181func (p * connPool ) Clean (network , address string ) {
227- if v , ok := p .conns .Load (address ); ok {
228- p .conns .Delete (address )
182+ if v , ok := p .conns .LoadAndDelete (address ); ok {
229183 v .(* transports ).close ()
230184 }
231185}
232186
233187// Close is to release resource of ConnPool, it is executed when client is closed.
234188func (p * connPool ) Close () error {
189+ if ! p .casClosed () {
190+ return nil
191+ }
192+
235193 p .conns .Range (func (addr , trans interface {}) bool {
236194 p .conns .Delete (addr )
237195 trans .(* transports ).close ()
@@ -240,6 +198,19 @@ func (p *connPool) Close() error {
240198 return nil
241199}
242200
201+ func (p * connPool ) isClosed () bool {
202+ return atomic .LoadInt32 (& p .closed ) == poolClosed
203+ }
204+
205+ func (p * connPool ) casClosed () bool {
206+ return atomic .CompareAndSwapInt32 (& p .closed , poolOpen , poolClosed )
207+ }
208+
209+ type dumpEntry struct {
210+ addr string
211+ tr grpc.ClientTransport
212+ }
213+
243214// Dump dumps the connection pool with the details of the underlying transport.
244215func (p * connPool ) Dump () interface {} {
245216 defer func () {
@@ -251,32 +222,70 @@ func (p *connPool) Dump() interface{} {
251222 // remoteAddress -> []clientTransport, where each clientTransport is a connection. Distinguish the connection via localAddress.
252223 // If mesh egress is not enabled, toAddr should be the address of the callee service.
253224 // Otherwise, toAddr will be the same, so you should check the remoteAddress in each stream, which is read from the header.
225+
226+ // sync.Map does not expose its length directly.
227+ // Since dump is a cold-path operation, performance is not a major concern here
254228 poolDump := make (map [string ]interface {}, p .size )
229+ var cliTransDumps []dumpEntry
255230 p .conns .Range (func (k , v interface {}) bool {
256231 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
232+ for _ , tr := range v .(* transports ).loadAll () {
233+ cliTransDumps = append (cliTransDumps , dumpEntry {addr : addr , tr : tr })
274234 }
275235 return true
276236 })
237+
238+ for _ , cliTransDump := range cliTransDumps {
239+ dumper , ok := cliTransDump .tr .(interface { Dump () interface {} })
240+ if ! ok {
241+ continue
242+ }
243+ var curr []interface {}
244+ if poolDump [cliTransDump .addr ] == nil {
245+ curr = make ([]interface {}, 0 )
246+ } else {
247+ curr = poolDump [cliTransDump .addr ].([]interface {})
248+ }
249+ curr = append (curr , dumper .Dump ())
250+ poolDump [cliTransDump .addr ] = curr
251+ }
277252 return poolDump
278253}
279254
255+ // newTransport creates a gRPC connection
256+ func newTransport (remoteService string ,
257+ dialer remote.Dialer , network , address string , connectTimeout time.Duration , opts grpc.ConnectOptions ,
258+ onGoAway func (context.Context , grpc.ClientTransport , grpc.GoAwayReason ),
259+ onClose func (context.Context , grpc.ClientTransport , error ),
260+ ) (grpc.ClientTransport , error ) {
261+ conn , err := dialer .DialTimeout (network , address , connectTimeout )
262+ if err != nil {
263+ return nil , err
264+ }
265+ if opts .TLSConfig != nil {
266+ tlsConn , tErr := newTLSConn (conn , opts .TLSConfig )
267+ if tErr != nil {
268+ // release tls handshake failed connection
269+ cErr := conn .Close ()
270+ if cErr != nil {
271+ klog .Warnf ("KITEX: Close TLS handshake failed connection, err: %v" , cErr )
272+ }
273+ return nil , tErr
274+ }
275+ conn = tlsConn
276+ }
277+ return grpc .NewClientTransportWithConfig (
278+ context .Background (), // gRPC connection does not need to be bound to a specific ctx
279+ conn ,
280+ opts ,
281+ grpc.ClientConfig {
282+ RemoteService : remoteService ,
283+ OnGoAway : onGoAway ,
284+ OnClose : onClose ,
285+ },
286+ )
287+ }
288+
280289// newTLSConn constructs a client-side TLS connection and performs handshake.
281290func newTLSConn (conn net.Conn , tlsCfg * tls.Config ) (net.Conn , error ) {
282291 tlsConn := tls .Client (conn , tlsCfg )
@@ -285,3 +294,12 @@ func newTLSConn(conn net.Conn, tlsCfg *tls.Config) (net.Conn, error) {
285294 }
286295 return tlsConn , nil
287296}
297+
298+ func checkActive (trans grpc.ClientTransport ) bool {
299+ if trans == nil {
300+ return false
301+ }
302+ // grpc.ClientTransport is implemented by *http2Client in pkg/remote/trans/nphttp2/grpc
303+ // it implements grpc.IsActive
304+ return trans .(grpc.IsActive ).IsActive ()
305+ }
0 commit comments