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  }