github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/client/grpc/pool.go (about)

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