go-micro.dev/v5@v5.12.0/client/grpc/grpc_pool.go (about)

     1  package grpc
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"time"
     7  
     8  	"google.golang.org/grpc"
     9  	"google.golang.org/grpc/connectivity"
    10  )
    11  
    12  type pool struct {
    13  	size int
    14  	ttl  int64
    15  
    16  	//  max streams on a *poolConn
    17  	maxStreams int
    18  	//  max idle conns
    19  	maxIdle int
    20  
    21  	sync.Mutex
    22  	conns map[string]*streamsPool
    23  }
    24  
    25  type streamsPool struct {
    26  	//  head of list
    27  	head *poolConn
    28  	//  busy conns list
    29  	busy *poolConn
    30  	//  the size of list
    31  	count int
    32  	//  idle conn
    33  	idle int
    34  }
    35  
    36  type poolConn struct {
    37  	//  grpc conn
    38  	*grpc.ClientConn
    39  	err  error
    40  	addr string
    41  
    42  	//  pool and streams pool
    43  	pool    *pool
    44  	sp      *streamsPool
    45  	streams int
    46  	created int64
    47  
    48  	//  list
    49  	pre  *poolConn
    50  	next *poolConn
    51  	in   bool
    52  }
    53  
    54  func newPool(size int, ttl time.Duration, idle int, ms int) *pool {
    55  	if ms <= 0 {
    56  		ms = 1
    57  	}
    58  	if idle < 0 {
    59  		idle = 0
    60  	}
    61  	return &pool{
    62  		size:       size,
    63  		ttl:        int64(ttl.Seconds()),
    64  		maxStreams: ms,
    65  		maxIdle:    idle,
    66  		conns:      make(map[string]*streamsPool),
    67  	}
    68  }
    69  
    70  func (p *pool) getConn(dialCtx context.Context, addr string, opts ...grpc.DialOption) (*poolConn, error) {
    71  	now := time.Now().Unix()
    72  	p.Lock()
    73  	sp, ok := p.conns[addr]
    74  	if !ok {
    75  		sp = &streamsPool{head: &poolConn{}, busy: &poolConn{}, count: 0, idle: 0}
    76  		p.conns[addr] = sp
    77  	}
    78  	//  while we have conns check streams and then return one
    79  	//  otherwise we'll create a new conn
    80  	conn := sp.head.next
    81  	for conn != nil {
    82  		//  check conn state
    83  		// https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md
    84  		switch conn.GetState() {
    85  		case connectivity.Connecting:
    86  			conn = conn.next
    87  			continue
    88  		case connectivity.Shutdown:
    89  			next := conn.next
    90  			if conn.streams == 0 {
    91  				removeConn(conn)
    92  				sp.idle--
    93  			}
    94  			conn = next
    95  			continue
    96  		case connectivity.TransientFailure:
    97  			next := conn.next
    98  			if conn.streams == 0 {
    99  				removeConn(conn)
   100  				conn.ClientConn.Close()
   101  				sp.idle--
   102  			}
   103  			conn = next
   104  			continue
   105  		case connectivity.Ready:
   106  		case connectivity.Idle:
   107  		}
   108  		//  a old conn
   109  		if now-conn.created > p.ttl {
   110  			next := conn.next
   111  			if conn.streams == 0 {
   112  				removeConn(conn)
   113  				conn.ClientConn.Close()
   114  				sp.idle--
   115  			}
   116  			conn = next
   117  			continue
   118  		}
   119  		//  a busy conn
   120  		if conn.streams >= p.maxStreams {
   121  			next := conn.next
   122  			removeConn(conn)
   123  			addConnAfter(conn, sp.busy)
   124  			conn = next
   125  			continue
   126  		}
   127  		//  a idle conn
   128  		if conn.streams == 0 {
   129  			sp.idle--
   130  		}
   131  		//  a good conn
   132  		conn.streams++
   133  		p.Unlock()
   134  		return conn, nil
   135  	}
   136  	p.Unlock()
   137  
   138  	//  create new conn
   139  	cc, err := grpc.DialContext(dialCtx, addr, opts...)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	conn = &poolConn{cc, nil, addr, p, sp, 1, time.Now().Unix(), nil, nil, false}
   144  
   145  	//  add conn to streams pool
   146  	p.Lock()
   147  	if sp.count < p.size {
   148  		addConnAfter(conn, sp.head)
   149  	}
   150  	p.Unlock()
   151  
   152  	return conn, nil
   153  }
   154  
   155  func (p *pool) release(addr string, conn *poolConn, err error) {
   156  	p.Lock()
   157  	p, sp, created := conn.pool, conn.sp, conn.created
   158  	//  try to add conn
   159  	if !conn.in && sp.count < p.size {
   160  		addConnAfter(conn, sp.head)
   161  	}
   162  	if !conn.in {
   163  		p.Unlock()
   164  		conn.ClientConn.Close()
   165  		return
   166  	}
   167  	//  a busy conn
   168  	if conn.streams >= p.maxStreams {
   169  		removeConn(conn)
   170  		addConnAfter(conn, sp.head)
   171  	}
   172  	conn.streams--
   173  	//  if streams == 0, we can do something
   174  	if conn.streams == 0 {
   175  		//  1. it has errored
   176  		//  2. too many idle conn or
   177  		//  3. conn is too old
   178  		now := time.Now().Unix()
   179  		if err != nil || sp.idle >= p.maxIdle || now-created > p.ttl {
   180  			removeConn(conn)
   181  			p.Unlock()
   182  			conn.ClientConn.Close()
   183  			return
   184  		}
   185  		sp.idle++
   186  	}
   187  	p.Unlock()
   188  }
   189  
   190  func (conn *poolConn) Close() {
   191  	conn.pool.release(conn.addr, conn, conn.err)
   192  }
   193  
   194  func removeConn(conn *poolConn) {
   195  	if conn.pre != nil {
   196  		conn.pre.next = conn.next
   197  	}
   198  	if conn.next != nil {
   199  		conn.next.pre = conn.pre
   200  	}
   201  	conn.pre = nil
   202  	conn.next = nil
   203  	conn.in = false
   204  	conn.sp.count--
   205  	return
   206  }
   207  
   208  func addConnAfter(conn *poolConn, after *poolConn) {
   209  	conn.next = after.next
   210  	conn.pre = after
   211  	if after.next != nil {
   212  		after.next.pre = conn
   213  	}
   214  	after.next = conn
   215  	conn.in = true
   216  	conn.sp.count++
   217  	return
   218  }