trpc.group/trpc-go/trpc-go@v1.0.3/pool/connpool/connection_pool.go (about) 1 // 2 // 3 // Tencent is pleased to support the open source community by making tRPC available. 4 // 5 // Copyright (C) 2023 THL A29 Limited, a Tencent company. 6 // All rights reserved. 7 // 8 // If you have downloaded a copy of the tRPC source code from Tencent, 9 // please note that tRPC source code is licensed under the Apache 2.0 License, 10 // A copy of the Apache 2.0 License is included in this file. 11 // 12 // 13 14 package connpool 15 16 import ( 17 "context" 18 "errors" 19 "io" 20 "net" 21 "strings" 22 "sync" 23 "sync/atomic" 24 "time" 25 26 "trpc.group/trpc-go/trpc-go/codec" 27 "trpc.group/trpc-go/trpc-go/internal/report" 28 "trpc.group/trpc-go/trpc-go/log" 29 ) 30 31 const ( 32 defaultDialTimeout = 200 * time.Millisecond 33 defaultIdleTimeout = 50 * time.Second 34 defaultMaxIdle = 65536 35 defaultCheckInterval = 3 * time.Second 36 defaultPoolIdleTimeout = 2 * defaultIdleTimeout 37 ) 38 39 var globalBuffer []byte = make([]byte, 1) 40 41 // DefaultConnectionPool is the default connection pool, replaceable. 42 var DefaultConnectionPool = NewConnectionPool() 43 44 // connection pool error message. 45 var ( 46 ErrPoolLimit = errors.New("connection pool limit") // ErrPoolLimit number of connections exceeds the limit error. 47 ErrPoolClosed = errors.New("connection pool closed") // ErrPoolClosed connection pool closed error. 48 ErrConnClosed = errors.New("conn closed") // ErrConnClosed connection closed. 49 ErrNoDeadline = errors.New("dial no deadline") // ErrNoDeadline has no deadline set. 50 ErrConnInPool = errors.New("conn already in pool") // ErrNoDeadline has no deadline set. 51 ) 52 53 // HealthChecker idle connection health check function. 54 // The function supports quick check and comprehensive check. 55 // Quick check is called when an idle connection is obtained, 56 // and only checks whether the connection status is abnormal. 57 // The function returns true to indicate that the connection is available normally. 58 type HealthChecker func(pc *PoolConn, isFast bool) bool 59 60 // NewConnectionPool creates a connection pool. 61 func NewConnectionPool(opt ...Option) Pool { 62 // Default value, tentative, need to debug to determine the specific value. 63 opts := &Options{ 64 MaxIdle: defaultMaxIdle, 65 IdleTimeout: defaultIdleTimeout, 66 DialTimeout: defaultDialTimeout, 67 PoolIdleTimeout: defaultPoolIdleTimeout, 68 Dial: Dial, 69 } 70 for _, o := range opt { 71 o(opts) 72 } 73 return &pool{ 74 opts: opts, 75 connectionPools: new(sync.Map), 76 } 77 } 78 79 // pool connection pool factory, maintains connection pools corresponding to all addresses, 80 // and connection pool option information. 81 type pool struct { 82 opts *Options 83 connectionPools *sync.Map 84 } 85 86 type dialFunc = func(ctx context.Context) (net.Conn, error) 87 88 func (p *pool) getDialFunc(network string, address string, opts GetOptions) dialFunc { 89 dialOpts := &DialOptions{ 90 Network: network, 91 Address: address, 92 LocalAddr: opts.LocalAddr, 93 CACertFile: opts.CACertFile, 94 TLSCertFile: opts.TLSCertFile, 95 TLSKeyFile: opts.TLSKeyFile, 96 TLSServerName: opts.TLSServerName, 97 IdleTimeout: p.opts.IdleTimeout, 98 } 99 100 return func(ctx context.Context) (net.Conn, error) { 101 select { 102 case <-ctx.Done(): 103 return nil, ctx.Err() 104 default: 105 } 106 d, ok := ctx.Deadline() 107 if !ok { 108 return nil, ErrNoDeadline 109 } 110 111 opts := *dialOpts 112 opts.Timeout = time.Until(d) 113 return p.opts.Dial(&opts) 114 } 115 } 116 117 // Get is used to get the connection from the connection pool. 118 func (p *pool) Get(network string, address string, opts GetOptions) (net.Conn, error) { 119 ctx, cancel := opts.getDialCtx(p.opts.DialTimeout) 120 if cancel != nil { 121 defer cancel() 122 } 123 key := getNodeKey(network, address, opts.Protocol) 124 if v, ok := p.connectionPools.Load(key); ok { 125 return v.(*ConnectionPool).Get(ctx) 126 } 127 128 newPool := &ConnectionPool{ 129 Dial: p.getDialFunc(network, address, opts), 130 MinIdle: p.opts.MinIdle, 131 MaxIdle: p.opts.MaxIdle, 132 MaxActive: p.opts.MaxActive, 133 Wait: p.opts.Wait, 134 MaxConnLifetime: p.opts.MaxConnLifetime, 135 IdleTimeout: p.opts.IdleTimeout, 136 framerBuilder: opts.FramerBuilder, 137 customReader: opts.CustomReader, 138 forceClosed: p.opts.ForceClose, 139 PushIdleConnToTail: p.opts.PushIdleConnToTail, 140 onCloseFunc: func() { p.connectionPools.Delete(key) }, 141 poolIdleTimeout: p.opts.PoolIdleTimeout, 142 } 143 144 if newPool.MaxActive > 0 { 145 newPool.token = make(chan struct{}, p.opts.MaxActive) 146 } 147 148 newPool.checker = newPool.defaultChecker 149 if p.opts.Checker != nil { 150 newPool.checker = p.opts.Checker 151 } 152 153 // Avoid the problem of writing concurrently to the pool map during initialization. 154 v, ok := p.connectionPools.LoadOrStore(key, newPool) 155 if !ok { 156 newPool.RegisterChecker(defaultCheckInterval, newPool.checker) 157 newPool.keepMinIdles() 158 return newPool.Get(ctx) 159 } 160 return v.(*ConnectionPool).Get(ctx) 161 } 162 163 // ConnectionPool is the connection pool. 164 type ConnectionPool struct { 165 Dial func(context.Context) (net.Conn, error) // initialize the connection. 166 MinIdle int // Minimum number of idle connections. 167 MaxIdle int // Maximum number of idle connections, 0 means no limit. 168 MaxActive int // Maximum number of active connections, 0 means no limit. 169 IdleTimeout time.Duration // idle connection timeout. 170 // Whether to wait when the maximum number of active connections is reached. 171 Wait bool 172 MaxConnLifetime time.Duration // Maximum lifetime of the connection. 173 mu sync.Mutex // Control concurrent locks. 174 checker HealthChecker // Idle connection health check function. 175 closed bool // Whether the connection pool has been closed. 176 token chan struct{} // control concurrency by applying token. 177 idleSize int // idle connections size. 178 idle connList // idle connection list. 179 framerBuilder codec.FramerBuilder 180 forceClosed bool // Force close the connection, suitable for streaming scenarios. 181 PushIdleConnToTail bool // connection to ip will be push tail when ConnectionPool.put method is called. 182 // customReader creates a reader encapsulating the underlying connection. 183 customReader func(io.Reader) io.Reader 184 onCloseFunc func() // execute when checker goroutine judge the connection_pool is useless. 185 used int32 // size of connections used by user, atomic. 186 lastGetTime int64 // last get connection millisecond timestamp, atomic. 187 poolIdleTimeout time.Duration // pool idle timeout. 188 } 189 190 func (p *ConnectionPool) keepMinIdles() { 191 p.mu.Lock() 192 count := p.MinIdle - p.idleSize 193 if count > 0 { 194 p.idleSize += count 195 } 196 p.mu.Unlock() 197 198 for i := 0; i < count; i++ { 199 go func() { 200 ctx, cancel := context.WithTimeout(context.Background(), defaultDialTimeout) 201 defer cancel() 202 if err := p.addIdleConn(ctx); err != nil { 203 p.mu.Lock() 204 p.idleSize-- 205 p.mu.Unlock() 206 } 207 }() 208 } 209 } 210 211 func (p *ConnectionPool) addIdleConn(ctx context.Context) error { 212 p.mu.Lock() 213 if p.closed { 214 p.mu.Unlock() 215 return ErrPoolClosed 216 } 217 p.mu.Unlock() 218 219 c, err := p.dial(ctx) 220 if err != nil { 221 return err 222 } 223 224 // put in idle list 225 pc := p.newPoolConn(c) 226 p.mu.Lock() 227 if p.closed { 228 pc.closed = true 229 pc.Conn.Close() 230 } else { 231 pc.t = time.Now() 232 if !p.PushIdleConnToTail { 233 p.idle.pushHead(pc) 234 } else { 235 p.idle.pushTail(pc) 236 } 237 } 238 p.mu.Unlock() 239 return nil 240 } 241 242 // Get gets the connection from the connection pool. 243 func (p *ConnectionPool) Get(ctx context.Context) (*PoolConn, error) { 244 var ( 245 pc *PoolConn 246 err error 247 ) 248 if pc, err = p.get(ctx); err != nil { 249 report.ConnectionPoolGetConnectionErr.Incr() 250 return nil, err 251 } 252 return pc, nil 253 } 254 255 // Close releases the connection. 256 func (p *ConnectionPool) Close() error { 257 p.mu.Lock() 258 if p.closed { 259 p.mu.Unlock() 260 return nil 261 } 262 p.closed = true 263 p.idle.count = 0 264 p.idleSize = 0 265 pc := p.idle.head 266 p.idle.head, p.idle.tail = nil, nil 267 p.mu.Unlock() 268 for ; pc != nil; pc = pc.next { 269 pc.Conn.Close() 270 pc.closed = true 271 } 272 return nil 273 } 274 275 // get gets the connection from the connection pool. 276 func (p *ConnectionPool) get(ctx context.Context) (*PoolConn, error) { 277 if err := p.getToken(ctx); err != nil { 278 return nil, err 279 } 280 281 atomic.StoreInt64(&p.lastGetTime, time.Now().UnixMilli()) 282 atomic.AddInt32(&p.used, 1) 283 284 // try to get an idle connection. 285 if pc := p.getIdleConn(); pc != nil { 286 return pc, nil 287 } 288 289 // get new connection. 290 pc, err := p.getNewConn(ctx) 291 if err != nil { 292 p.freeToken() 293 return nil, err 294 } 295 return pc, nil 296 } 297 298 // if p.Wait is True, return err when timeout. 299 // if p.Wait is False, return err when token empty immediately. 300 func (p *ConnectionPool) getToken(ctx context.Context) error { 301 if p.MaxActive <= 0 { 302 return nil 303 } 304 305 if p.Wait { 306 select { 307 case p.token <- struct{}{}: 308 return nil 309 case <-ctx.Done(): 310 return ctx.Err() 311 } 312 } else { 313 select { 314 case p.token <- struct{}{}: 315 return nil 316 default: 317 return ErrPoolLimit 318 } 319 } 320 } 321 322 func (p *ConnectionPool) freeToken() { 323 if p.MaxActive <= 0 { 324 return 325 } 326 <-p.token 327 } 328 329 func (p *ConnectionPool) getIdleConn() *PoolConn { 330 p.mu.Lock() 331 for p.idle.head != nil { 332 pc := p.idle.head 333 p.idle.popHead() 334 p.idleSize-- 335 p.mu.Unlock() 336 if p.checker(pc, true) { 337 return pc 338 } 339 pc.Conn.Close() 340 pc.closed = true 341 p.mu.Lock() 342 } 343 p.mu.Unlock() 344 return nil 345 } 346 347 func (p *ConnectionPool) getNewConn(ctx context.Context) (*PoolConn, error) { 348 // If the connection pool has been closed, return an error directly. 349 p.mu.Lock() 350 if p.closed { 351 p.mu.Unlock() 352 return nil, ErrPoolClosed 353 } 354 p.mu.Unlock() 355 356 c, err := p.dial(ctx) 357 if err != nil { 358 return nil, err 359 } 360 361 report.ConnectionPoolGetNewConnection.Incr() 362 return p.newPoolConn(c), nil 363 } 364 365 func (p *ConnectionPool) newPoolConn(c net.Conn) *PoolConn { 366 pc := &PoolConn{ 367 Conn: c, 368 created: time.Now(), 369 pool: p, 370 forceClose: p.forceClosed, 371 inPool: false, 372 } 373 if p.framerBuilder != nil { 374 pc.fr = p.framerBuilder.New(p.customReader(pc)) 375 pc.copyFrame = !codec.IsSafeFramer(pc.fr) 376 } 377 return pc 378 } 379 380 func (p *ConnectionPool) checkHealthOnce() { 381 p.mu.Lock() 382 n := p.idle.count 383 for i := 0; i < n && p.idle.head != nil; i++ { 384 pc := p.idle.head 385 p.idle.popHead() 386 p.idleSize-- 387 p.mu.Unlock() 388 if p.checker(pc, false) { 389 p.mu.Lock() 390 p.idleSize++ 391 p.idle.pushTail(pc) 392 } else { 393 pc.Conn.Close() 394 pc.closed = true 395 p.mu.Lock() 396 } 397 } 398 p.mu.Unlock() 399 } 400 401 func (p *ConnectionPool) checkRoutine(interval time.Duration) { 402 for { 403 time.Sleep(interval) 404 p.mu.Lock() 405 closed := p.closed 406 p.mu.Unlock() 407 if closed { 408 return 409 } 410 p.checkHealthOnce() 411 412 if p.checkPoolIdleTimeout() { 413 return 414 } 415 416 // Check if the minimum number of idle connections is met. 417 p.checkMinIdle() 418 } 419 } 420 421 func (p *ConnectionPool) checkMinIdle() { 422 if p.MinIdle <= 0 { 423 return 424 } 425 p.keepMinIdles() 426 } 427 428 // checkPoolIdleTimeout check whether the connection_pool is useless 429 func (p *ConnectionPool) checkPoolIdleTimeout() bool { 430 p.mu.Lock() 431 lastGetTime := atomic.LoadInt64(&p.lastGetTime) 432 if lastGetTime == 0 || p.poolIdleTimeout == 0 { 433 p.mu.Unlock() 434 return false 435 } 436 if time.Now().UnixMilli()-lastGetTime > p.poolIdleTimeout.Milliseconds() && 437 p.onCloseFunc != nil && atomic.LoadInt32(&p.used) == 0 { 438 p.mu.Unlock() 439 p.onCloseFunc() 440 if err := p.Close(); err != nil { 441 log.Errorf("failed to close ConnectionPool, error: %v", err) 442 } 443 return true 444 } 445 p.mu.Unlock() 446 return false 447 } 448 449 // RegisterChecker registers the idle connection check method. 450 func (p *ConnectionPool) RegisterChecker(interval time.Duration, checker HealthChecker) { 451 if interval <= 0 || checker == nil { 452 return 453 } 454 p.mu.Lock() 455 p.checker = checker 456 p.mu.Unlock() 457 go p.checkRoutine(interval) 458 } 459 460 // defaultChecker is the default idle connection check method, 461 // returning true means the connection is available normally. 462 func (p *ConnectionPool) defaultChecker(pc *PoolConn, isFast bool) bool { 463 // Check whether the connection status is abnormal: 464 // closed, network exception or sticky packet processing exception. 465 if pc.isRemoteError(isFast) { 466 return false 467 } 468 // Based on performance considerations, the quick check only does the RemoteErr check. 469 if isFast { 470 return true 471 } 472 // Check if the connection has exceeded the maximum idle time, if so close the connection. 473 if p.IdleTimeout > 0 && pc.t.Add(p.IdleTimeout).Before(time.Now()) { 474 report.ConnectionPoolIdleTimeout.Incr() 475 return false 476 } 477 // Check if the connection is still alive. 478 if p.MaxConnLifetime > 0 && pc.created.Add(p.MaxConnLifetime).Before(time.Now()) { 479 report.ConnectionPoolLifetimeExceed.Incr() 480 return false 481 } 482 return true 483 } 484 485 // dial establishes a connection. 486 func (p *ConnectionPool) dial(ctx context.Context) (net.Conn, error) { 487 if p.Dial != nil { 488 return p.Dial(ctx) 489 } 490 return nil, errors.New("must pass Dial to pool") 491 } 492 493 // put tries to release the connection to the connection pool. 494 // forceClose depends on GetOptions.ForceClose and will be true 495 // if the connection fails to read or write. 496 func (p *ConnectionPool) put(pc *PoolConn, forceClose bool) error { 497 if pc.closed { 498 return nil 499 } 500 p.mu.Lock() 501 if !p.closed && !forceClose { 502 pc.t = time.Now() 503 if !p.PushIdleConnToTail { 504 p.idle.pushHead(pc) 505 } else { 506 p.idle.pushTail(pc) 507 } 508 if p.idleSize >= p.MaxIdle { 509 pc = p.idle.tail 510 p.idle.popTail() 511 } else { 512 p.idleSize++ 513 pc = nil 514 } 515 } 516 p.mu.Unlock() 517 if pc != nil { 518 pc.closed = true 519 pc.Conn.Close() 520 } 521 p.freeToken() 522 atomic.AddInt32(&p.used, -1) 523 return nil 524 } 525 526 // PoolConn is the connection in the connection pool. 527 type PoolConn struct { 528 net.Conn 529 fr codec.Framer 530 t time.Time 531 created time.Time 532 next, prev *PoolConn 533 pool *ConnectionPool 534 closed bool 535 forceClose bool 536 copyFrame bool 537 inPool bool 538 } 539 540 // ReadFrame reads the frame. 541 func (pc *PoolConn) ReadFrame() ([]byte, error) { 542 if pc.closed { 543 return nil, ErrConnClosed 544 } 545 if pc.fr == nil { 546 pc.pool.put(pc, true) 547 return nil, errors.New("framer not set") 548 } 549 data, err := pc.fr.ReadFrame() 550 if err != nil { 551 // ReadFrame failure may be socket Read interface timeout failure 552 // or the unpacking fails, in both cases the connection should be closed. 553 pc.pool.put(pc, true) 554 return nil, err 555 } 556 557 // Framer does not support concurrent read safety, copy the data. 558 if pc.copyFrame { 559 buf := make([]byte, len(data)) 560 copy(buf, data) 561 return buf, err 562 } 563 return data, err 564 } 565 566 // isRemoteError tries to receive a byte to detect whether the peer has actively closed the connection. 567 // If the peer returns an io.EOF error, it is indicated that the peer has been closed. 568 // Idle connections should not read data, if the data is read, it means the upper layer's 569 // sticky packet processing is not done, the connection should also be discarded. 570 // return true if there is an error in the connection. 571 func (pc *PoolConn) isRemoteError(isFast bool) bool { 572 var err error 573 if isFast { 574 err = checkConnErrUnblock(pc.Conn, globalBuffer) 575 } else { 576 err = checkConnErr(pc.Conn, globalBuffer) 577 } 578 if err != nil { 579 report.ConnectionPoolRemoteErr.Incr() 580 return true 581 } 582 return false 583 } 584 585 // reset resets the connection state. 586 func (pc *PoolConn) reset() { 587 if pc == nil { 588 return 589 } 590 pc.Conn.SetDeadline(time.Time{}) 591 } 592 593 // Write sends data on the connection. 594 func (pc *PoolConn) Write(b []byte) (int, error) { 595 if pc.closed { 596 return 0, ErrConnClosed 597 } 598 n, err := pc.Conn.Write(b) 599 if err != nil { 600 pc.pool.put(pc, true) 601 } 602 return n, err 603 } 604 605 // Read reads data on the connection. 606 func (pc *PoolConn) Read(b []byte) (int, error) { 607 if pc.closed { 608 return 0, ErrConnClosed 609 } 610 n, err := pc.Conn.Read(b) 611 if err != nil { 612 pc.pool.put(pc, true) 613 } 614 return n, err 615 } 616 617 // Close overrides the Close method of net.Conn and puts it back into the connection pool. 618 func (pc *PoolConn) Close() error { 619 if pc.closed { 620 return ErrConnClosed 621 } 622 if pc.inPool { 623 return ErrConnInPool 624 } 625 pc.reset() 626 return pc.pool.put(pc, pc.forceClose) 627 } 628 629 // GetRawConn gets raw connection in PoolConn. 630 func (pc *PoolConn) GetRawConn() net.Conn { 631 return pc.Conn 632 } 633 634 // connList maintains idle connections and uses stacks to maintain connections. 635 // 636 // The stack method has an advantage over the queue. When the request volume is relatively small but the request 637 // distribution is still relatively uniform, the queue method will cause the occupied connection to be delayed. 638 type connList struct { 639 count int 640 head, tail *PoolConn 641 } 642 643 func (l *connList) pushHead(pc *PoolConn) { 644 pc.inPool = true 645 pc.next = l.head 646 pc.prev = nil 647 if l.count == 0 { 648 l.tail = pc 649 } else { 650 l.head.prev = pc 651 } 652 l.count++ 653 l.head = pc 654 } 655 656 func (l *connList) popHead() { 657 pc := l.head 658 l.count-- 659 if l.count == 0 { 660 l.head, l.tail = nil, nil 661 } else { 662 pc.next.prev = nil 663 l.head = pc.next 664 } 665 pc.next, pc.prev = nil, nil 666 pc.inPool = false 667 } 668 669 func (l *connList) pushTail(pc *PoolConn) { 670 pc.inPool = true 671 pc.next = nil 672 pc.prev = l.tail 673 if l.count == 0 { 674 l.head = pc 675 } else { 676 l.tail.next = pc 677 } 678 l.count++ 679 l.tail = pc 680 } 681 682 func (l *connList) popTail() { 683 pc := l.tail 684 l.count-- 685 if l.count == 0 { 686 l.head, l.tail = nil, nil 687 } else { 688 pc.prev.next = nil 689 l.tail = pc.prev 690 } 691 pc.next, pc.prev = nil, nil 692 pc.inPool = false 693 } 694 695 func getNodeKey(network, address, protocol string) string { 696 const underline = "_" 697 var key strings.Builder 698 key.Grow(len(network) + len(address) + len(protocol) + 2) 699 key.WriteString(network) 700 key.WriteString(underline) 701 key.WriteString(address) 702 key.WriteString(underline) 703 key.WriteString(protocol) 704 return key.String() 705 }