github.com/cloudwego/kitex@v0.9.0/pkg/remote/connpool/long_pool.go (about) 1 /* 2 * Copyright 2021 CloudWeGo Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // Package connpool provide short connection and long connection pool. 18 package connpool 19 20 import ( 21 "context" 22 "fmt" 23 "net" 24 "sync" 25 "sync/atomic" 26 "time" 27 28 "github.com/cloudwego/kitex/pkg/connpool" 29 "github.com/cloudwego/kitex/pkg/remote" 30 "github.com/cloudwego/kitex/pkg/utils" 31 "github.com/cloudwego/kitex/pkg/warmup" 32 ) 33 34 var ( 35 _ net.Conn = &longConn{} 36 _ remote.LongConnPool = &LongPool{} 37 38 // global shared tickers for different LongPool 39 sharedTickers sync.Map 40 ) 41 42 const ( 43 configDumpKey = "idle_config" 44 ) 45 46 func getSharedTicker(p *LongPool, refreshInterval time.Duration) *utils.SharedTicker { 47 sti, ok := sharedTickers.Load(refreshInterval) 48 if ok { 49 st := sti.(*utils.SharedTicker) 50 st.Add(p) 51 return st 52 } 53 sti, _ = sharedTickers.LoadOrStore(refreshInterval, utils.NewSharedTicker(refreshInterval)) 54 st := sti.(*utils.SharedTicker) 55 st.Add(p) 56 return st 57 } 58 59 // netAddr implements the net.Addr interface and comparability. 60 type netAddr struct { 61 network string 62 address string 63 } 64 65 // Network implements the net.Addr interface. 66 func (na netAddr) Network() string { return na.network } 67 68 // String implements the net.Addr interface. 69 func (na netAddr) String() string { return na.address } 70 71 // longConn implements the net.Conn interface. 72 type longConn struct { 73 net.Conn 74 sync.RWMutex 75 deadline time.Time 76 address string 77 } 78 79 // Close implements the net.Conn interface. 80 func (c *longConn) Close() error { 81 return c.Conn.Close() 82 } 83 84 // RawConn returns the real underlying net.Conn. 85 func (c *longConn) RawConn() net.Conn { 86 return c.Conn 87 } 88 89 // IsActive indicates whether the connection is active. 90 func (c *longConn) IsActive() bool { 91 if conn, ok := c.Conn.(remote.IsActive); ok { 92 return conn.IsActive() 93 } else { 94 return time.Now().Before(c.deadline) 95 } 96 } 97 98 // Expired checks the deadline of the connection. 99 func (c *longConn) Expired() bool { 100 return time.Now().After(c.deadline) 101 } 102 103 type PoolDump struct { 104 IdleNum int `json:"idle_num"` 105 ConnsDeadline []time.Time `json:"conns_deadline"` 106 } 107 108 func newPool(minIdle, maxIdle int, maxIdleTimeout time.Duration) *pool { 109 p := &pool{ 110 idleList: make([]*longConn, 0, maxIdle), 111 minIdle: minIdle, 112 maxIdle: maxIdle, 113 maxIdleTimeout: maxIdleTimeout, 114 } 115 return p 116 } 117 118 // pool implements a pool of long connections. 119 type pool struct { 120 idleList []*longConn 121 mu sync.RWMutex 122 // config 123 minIdle int 124 maxIdle int // currIdle <= maxIdle. 125 maxIdleTimeout time.Duration // the idle connection will be cleaned if the idle time exceeds maxIdleTimeout. 126 } 127 128 // Get gets the first active connection from the idleList. Return the number of connections decreased during the Get. 129 func (p *pool) Get() (*longConn, bool, int) { 130 p.mu.Lock() 131 // Get the first active one 132 n := len(p.idleList) 133 i := n - 1 134 for ; i >= 0; i-- { 135 o := p.idleList[i] 136 if o.IsActive() { 137 p.idleList = p.idleList[:i] 138 p.mu.Unlock() 139 return o, true, n - i 140 } 141 // inactive object 142 o.Close() 143 } 144 // in case all objects are inactive 145 if i < 0 { 146 i = 0 147 } 148 p.idleList = p.idleList[:i] 149 p.mu.Unlock() 150 return nil, false, n - i 151 } 152 153 // Put puts back a connection to the pool. 154 func (p *pool) Put(o *longConn) bool { 155 p.mu.Lock() 156 var recycled bool 157 if len(p.idleList) < p.maxIdle { 158 o.deadline = time.Now().Add(p.maxIdleTimeout) 159 p.idleList = append(p.idleList, o) 160 recycled = true 161 } 162 p.mu.Unlock() 163 return recycled 164 } 165 166 // Evict cleanups the expired connections. 167 func (p *pool) Evict() int { 168 p.mu.Lock() 169 i := 0 170 for ; i < len(p.idleList)-p.minIdle; i++ { 171 if !p.idleList[i].Expired() { 172 break 173 } 174 // close the inactive object 175 p.idleList[i].Close() 176 } 177 p.idleList = p.idleList[i:] 178 p.mu.Unlock() 179 return i 180 } 181 182 // Len returns the length of the pool. 183 func (p *pool) Len() int { 184 p.mu.RLock() 185 l := len(p.idleList) 186 p.mu.RUnlock() 187 return l 188 } 189 190 // Close closes the pool and all the objects in the pool. 191 func (p *pool) Close() int { 192 p.mu.Lock() 193 num := len(p.idleList) 194 for i := 0; i < num; i++ { 195 p.idleList[i].Close() 196 } 197 p.idleList = nil 198 199 p.mu.Unlock() 200 return num 201 } 202 203 // Dump dumps the info of all the objects in the pool. 204 func (p *pool) Dump() PoolDump { 205 p.mu.RLock() 206 idleNum := len(p.idleList) 207 connsDeadline := make([]time.Time, idleNum) 208 for i := 0; i < idleNum; i++ { 209 connsDeadline[i] = p.idleList[i].deadline 210 } 211 s := PoolDump{ 212 IdleNum: idleNum, 213 ConnsDeadline: connsDeadline, 214 } 215 p.mu.RUnlock() 216 return s 217 } 218 219 func newPeer( 220 serviceName string, 221 addr net.Addr, 222 minIdle int, 223 maxIdle int, 224 maxIdleTimeout time.Duration, 225 globalIdle *utils.MaxCounter, 226 ) *peer { 227 return &peer{ 228 serviceName: serviceName, 229 addr: addr, 230 globalIdle: globalIdle, 231 pool: newPool(minIdle, maxIdle, maxIdleTimeout), 232 } 233 } 234 235 // peer has one address, it manages all connections base on this address 236 type peer struct { 237 // info 238 serviceName string 239 addr net.Addr 240 globalIdle *utils.MaxCounter 241 // pool 242 pool *pool 243 } 244 245 // Get gets a connection with dialer and timeout. Dial a new connection if no idle connection in pool is available. 246 func (p *peer) Get(d remote.Dialer, timeout time.Duration, reporter Reporter, addr string) (net.Conn, error) { 247 var c net.Conn 248 c, reused, decNum := p.pool.Get() 249 p.globalIdle.DecN(int64(decNum)) 250 if reused { 251 reporter.ReuseSucceed(Long, p.serviceName, p.addr) 252 return c, nil 253 } 254 // dial a new connection 255 c, err := d.DialTimeout(p.addr.Network(), p.addr.String(), timeout) 256 if err != nil { 257 reporter.ConnFailed(Long, p.serviceName, p.addr) 258 return nil, err 259 } 260 reporter.ConnSucceed(Long, p.serviceName, p.addr) 261 return &longConn{ 262 Conn: c, 263 address: addr, 264 }, nil 265 } 266 267 // Put puts a connection back to the peer. 268 func (p *peer) Put(c *longConn) error { 269 if !p.globalIdle.Inc() { 270 return c.Close() 271 } 272 if !p.pool.Put(c) { 273 p.globalIdle.Dec() 274 return c.Close() 275 } 276 return nil 277 } 278 279 func (p *peer) Len() int { 280 return p.pool.Len() 281 } 282 283 func (p *peer) Evict() { 284 n := p.pool.Evict() 285 p.globalIdle.DecN(int64(n)) 286 } 287 288 // Close closes the peer and all the connections in the ring. 289 func (p *peer) Close() { 290 n := p.pool.Close() 291 p.globalIdle.DecN(int64(n)) 292 } 293 294 // NewLongPool creates a long pool using the given IdleConfig. 295 func NewLongPool(serviceName string, idlConfig connpool.IdleConfig) *LongPool { 296 limit := utils.NewMaxCounter(idlConfig.MaxIdleGlobal) 297 lp := &LongPool{ 298 reporter: &DummyReporter{}, 299 globalIdle: limit, 300 newPeer: func(addr net.Addr) *peer { 301 return newPeer( 302 serviceName, 303 addr, 304 idlConfig.MinIdlePerAddress, 305 idlConfig.MaxIdlePerAddress, 306 idlConfig.MaxIdleTimeout, 307 limit) 308 }, 309 idleConfig: idlConfig, 310 } 311 // add this long pool into the sharedTicker 312 lp.sharedTicker = getSharedTicker(lp, idlConfig.MaxIdleTimeout) 313 return lp 314 } 315 316 // LongPool manages a pool of long connections. 317 type LongPool struct { 318 reporter Reporter 319 peerMap sync.Map 320 newPeer func(net.Addr) *peer 321 globalIdle *utils.MaxCounter 322 idleConfig connpool.IdleConfig 323 sharedTicker *utils.SharedTicker 324 closed int32 // active: 0, closed: 1 325 } 326 327 // Get pick or generate a net.Conn and return 328 // The context is not used but leave it for now. 329 func (lp *LongPool) Get(ctx context.Context, network, address string, opt remote.ConnOption) (net.Conn, error) { 330 addr := netAddr{network, address} 331 p := lp.getPeer(addr) 332 return p.Get(opt.Dialer, opt.ConnectTimeout, lp.reporter, address) 333 } 334 335 // Put implements the ConnPool interface. 336 func (lp *LongPool) Put(conn net.Conn) error { 337 c, ok := conn.(*longConn) 338 if !ok { 339 return conn.Close() 340 } 341 342 addr := conn.RemoteAddr() 343 na := netAddr{addr.Network(), c.address} 344 p, ok := lp.peerMap.Load(na) 345 if ok { 346 p.(*peer).Put(c) 347 return nil 348 } 349 return c.Conn.Close() 350 } 351 352 // Discard implements the ConnPool interface. 353 func (lp *LongPool) Discard(conn net.Conn) error { 354 c, ok := conn.(*longConn) 355 if ok { 356 return c.Close() 357 } 358 return conn.Close() 359 } 360 361 // Clean implements the LongConnPool interface. 362 func (lp *LongPool) Clean(network, address string) { 363 na := netAddr{network, address} 364 if p, ok := lp.peerMap.Load(na); ok { 365 lp.peerMap.Delete(na) 366 go p.(*peer).Close() 367 } 368 } 369 370 // Dump is used to dump current long pool info when needed, like debug query. 371 func (lp *LongPool) Dump() interface{} { 372 m := make(map[string]interface{}) 373 m[configDumpKey] = lp.idleConfig 374 lp.peerMap.Range(func(key, value interface{}) bool { 375 t := value.(*peer).pool.Dump() 376 m[key.(netAddr).String()] = t 377 return true 378 }) 379 return m 380 } 381 382 // Close releases all peers in the pool, it is executed when client is closed. 383 func (lp *LongPool) Close() error { 384 if !atomic.CompareAndSwapInt32(&lp.closed, 0, 1) { 385 return fmt.Errorf("long pool is already closed") 386 } 387 // close all peers 388 lp.peerMap.Range(func(addr, value interface{}) bool { 389 lp.peerMap.Delete(addr) 390 v := value.(*peer) 391 v.Close() 392 return true 393 }) 394 // remove from the shared ticker 395 lp.sharedTicker.Delete(lp) 396 return nil 397 } 398 399 // EnableReporter enable reporter for long connection pool. 400 func (lp *LongPool) EnableReporter() { 401 lp.reporter = GetCommonReporter() 402 } 403 404 // WarmUp implements the warmup.Pool interface. 405 func (lp *LongPool) WarmUp(eh warmup.ErrorHandling, wuo *warmup.PoolOption, co remote.ConnOption) error { 406 h := &warmup.PoolHelper{ErrorHandling: eh} 407 return h.WarmUp(wuo, lp, co) 408 } 409 410 // Evict cleanups the idle connections in peers. 411 func (lp *LongPool) Evict() { 412 if atomic.LoadInt32(&lp.closed) == 0 { 413 // Evict idle connections 414 lp.peerMap.Range(func(key, value interface{}) bool { 415 p := value.(*peer) 416 p.Evict() 417 return true 418 }) 419 } 420 } 421 422 // Tick implements the interface utils.TickerTask. 423 func (lp *LongPool) Tick() { 424 lp.Evict() 425 } 426 427 // getPeer gets a peer from the pool based on the addr, or create a new one if not exist. 428 func (lp *LongPool) getPeer(addr netAddr) *peer { 429 p, ok := lp.peerMap.Load(addr) 430 if ok { 431 return p.(*peer) 432 } 433 p, _ = lp.peerMap.LoadOrStore(addr, lp.newPeer(addr)) 434 return p.(*peer) 435 }