github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/conn/pool.go (about)

     1  package conn
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"sync/atomic"
     7  	"time"
     8  
     9  	"google.golang.org/grpc"
    10  	grpcCodes "google.golang.org/grpc/codes"
    11  
    12  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/closer"
    13  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/endpoint"
    14  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/stack"
    15  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xcontext"
    16  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
    17  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xsync"
    18  	"github.com/ydb-platform/ydb-go-sdk/v3/trace"
    19  )
    20  
    21  type connsKey struct {
    22  	address string
    23  	nodeID  uint32
    24  }
    25  
    26  type Pool struct {
    27  	usages int64
    28  	config Config
    29  	mtx    xsync.RWMutex
    30  	opts   []grpc.DialOption
    31  	conns  map[connsKey]*conn
    32  	done   chan struct{}
    33  }
    34  
    35  func (p *Pool) Get(endpoint endpoint.Endpoint) Conn {
    36  	p.mtx.Lock()
    37  	defer p.mtx.Unlock()
    38  
    39  	var (
    40  		address = endpoint.Address()
    41  		cc      *conn
    42  		has     bool
    43  	)
    44  
    45  	key := connsKey{address, endpoint.NodeID()}
    46  
    47  	if cc, has = p.conns[key]; has {
    48  		return cc
    49  	}
    50  
    51  	cc = newConn(
    52  		endpoint,
    53  		p.config,
    54  		withOnClose(p.remove),
    55  		withOnTransportError(p.Ban),
    56  	)
    57  
    58  	p.conns[key] = cc
    59  
    60  	return cc
    61  }
    62  
    63  func (p *Pool) remove(c *conn) {
    64  	p.mtx.Lock()
    65  	defer p.mtx.Unlock()
    66  	delete(p.conns, connsKey{c.Endpoint().Address(), c.Endpoint().NodeID()})
    67  }
    68  
    69  func (p *Pool) isClosed() bool {
    70  	select {
    71  	case <-p.done:
    72  		return true
    73  	default:
    74  		return false
    75  	}
    76  }
    77  
    78  func (p *Pool) Ban(ctx context.Context, cc Conn, cause error) {
    79  	if p.isClosed() {
    80  		return
    81  	}
    82  
    83  	if !xerrors.IsTransportError(cause,
    84  		grpcCodes.ResourceExhausted,
    85  		grpcCodes.Unavailable,
    86  		// grpcCodes.OK,
    87  		// grpcCodes.Canceled,
    88  		// grpcCodes.Unknown,
    89  		// grpcCodes.InvalidArgument,
    90  		// grpcCodes.DeadlineExceeded,
    91  		// grpcCodes.NotFound,
    92  		// grpcCodes.AlreadyExists,
    93  		// grpcCodes.PermissionDenied,
    94  		// grpcCodes.FailedPrecondition,
    95  		// grpcCodes.Aborted,
    96  		// grpcCodes.OutOfRange,
    97  		// grpcCodes.Unimplemented,
    98  		// grpcCodes.Internal,
    99  		// grpcCodes.DataLoss,
   100  		// grpcCodes.Unauthenticated,
   101  	) {
   102  		return
   103  	}
   104  
   105  	e := cc.Endpoint().Copy()
   106  
   107  	p.mtx.RLock()
   108  	defer p.mtx.RUnlock()
   109  
   110  	cc, ok := p.conns[connsKey{e.Address(), e.NodeID()}]
   111  	if !ok {
   112  		return
   113  	}
   114  
   115  	trace.DriverOnConnBan(
   116  		p.config.Trace(), &ctx,
   117  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/conn.(*Pool).Ban"),
   118  		e, cc.GetState(), cause,
   119  	)(cc.SetState(ctx, Banned))
   120  }
   121  
   122  func (p *Pool) Allow(ctx context.Context, cc Conn) {
   123  	if p.isClosed() {
   124  		return
   125  	}
   126  
   127  	e := cc.Endpoint().Copy()
   128  
   129  	p.mtx.RLock()
   130  	defer p.mtx.RUnlock()
   131  
   132  	cc, ok := p.conns[connsKey{e.Address(), e.NodeID()}]
   133  	if !ok {
   134  		return
   135  	}
   136  
   137  	trace.DriverOnConnAllow(
   138  		p.config.Trace(), &ctx,
   139  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/conn.(*Pool).Allow"),
   140  		e, cc.GetState(),
   141  	)(cc.Unban(ctx))
   142  }
   143  
   144  func (p *Pool) Take(context.Context) error {
   145  	atomic.AddInt64(&p.usages, 1)
   146  
   147  	return nil
   148  }
   149  
   150  func (p *Pool) Release(ctx context.Context) (finalErr error) {
   151  	onDone := trace.DriverOnPoolRelease(p.config.Trace(), &ctx,
   152  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/conn.(*Pool).Release"),
   153  	)
   154  	defer func() {
   155  		onDone(finalErr)
   156  	}()
   157  
   158  	if atomic.AddInt64(&p.usages, -1) > 0 {
   159  		return nil
   160  	}
   161  
   162  	close(p.done)
   163  
   164  	var conns []closer.Closer
   165  	p.mtx.WithRLock(func() {
   166  		conns = make([]closer.Closer, 0, len(p.conns))
   167  		for _, c := range p.conns {
   168  			conns = append(conns, c)
   169  		}
   170  	})
   171  
   172  	var (
   173  		errCh = make(chan error, len(conns))
   174  		wg    sync.WaitGroup
   175  	)
   176  
   177  	wg.Add(len(conns))
   178  	for _, c := range conns {
   179  		go func(c closer.Closer) {
   180  			defer wg.Done()
   181  			if err := c.Close(ctx); err != nil {
   182  				errCh <- err
   183  			}
   184  		}(c)
   185  	}
   186  	wg.Wait()
   187  	close(errCh)
   188  
   189  	issues := make([]error, 0, len(conns))
   190  	for err := range errCh {
   191  		issues = append(issues, err)
   192  	}
   193  
   194  	if len(issues) > 0 {
   195  		return xerrors.WithStackTrace(xerrors.NewWithIssues("connection pool close failed", issues...))
   196  	}
   197  
   198  	return nil
   199  }
   200  
   201  func (p *Pool) connParker(ctx context.Context, ttl, interval time.Duration) {
   202  	ticker := time.NewTicker(interval)
   203  	defer ticker.Stop()
   204  	for {
   205  		select {
   206  		case <-p.done:
   207  			return
   208  		case <-ticker.C:
   209  			for _, c := range p.collectConns() {
   210  				if time.Since(c.LastUsage()) > ttl {
   211  					switch c.GetState() {
   212  					case Online, Banned:
   213  						_ = c.park(ctx)
   214  					default:
   215  						// nop
   216  					}
   217  				}
   218  			}
   219  		}
   220  	}
   221  }
   222  
   223  func (p *Pool) collectConns() []*conn {
   224  	p.mtx.RLock()
   225  	defer p.mtx.RUnlock()
   226  	conns := make([]*conn, 0, len(p.conns))
   227  	for _, c := range p.conns {
   228  		conns = append(conns, c)
   229  	}
   230  
   231  	return conns
   232  }
   233  
   234  func NewPool(ctx context.Context, config Config) *Pool {
   235  	onDone := trace.DriverOnPoolNew(config.Trace(), &ctx,
   236  		stack.FunctionID("github.com/ydb-platform/ydb-go-sdk/v3/internal/conn.NewPool"),
   237  	)
   238  	defer onDone()
   239  
   240  	p := &Pool{
   241  		usages: 1,
   242  		config: config,
   243  		opts:   config.GrpcDialOptions(),
   244  		conns:  make(map[connsKey]*conn),
   245  		done:   make(chan struct{}),
   246  	}
   247  
   248  	if ttl := config.ConnectionTTL(); ttl > 0 {
   249  		go p.connParker(xcontext.ValueOnly(ctx), ttl, ttl/2) //nolint:gomnd
   250  	}
   251  
   252  	return p
   253  }