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 }