vitess.io/vitess@v0.16.2/go/vt/dbconnpool/connection_pool.go (about) 1 /* 2 Copyright 2019 The Vitess 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 /* 18 Package dbconnpool exposes a single DBConnection object 19 with wrapped access to a single DB connection, and a ConnectionPool 20 object to pool these DBConnections. 21 */ 22 package dbconnpool 23 24 import ( 25 "errors" 26 "net" 27 "sync" 28 "time" 29 30 "vitess.io/vitess/go/netutil" 31 32 "context" 33 34 "vitess.io/vitess/go/pools" 35 "vitess.io/vitess/go/stats" 36 "vitess.io/vitess/go/vt/dbconfigs" 37 ) 38 39 var ( 40 // ErrConnPoolClosed is returned if the connection pool is closed. 41 ErrConnPoolClosed = errors.New("connection pool is closed") 42 // usedNames is for preventing expvar from panicking. Tests 43 // create pool objects multiple time. If a name was previously 44 // used, expvar initialization is skipped. 45 // TODO(sougou): Find a way to still crash if this happened 46 // through non-test code. 47 usedNames = make(map[string]bool) 48 ) 49 50 // ConnectionPool re-exposes ResourcePool as a pool of 51 // PooledDBConnection objects. 52 type ConnectionPool struct { 53 mu sync.Mutex 54 connections pools.IResourcePool 55 capacity int 56 idleTimeout time.Duration 57 maxLifetime time.Duration 58 resolutionFrequency time.Duration 59 60 // info is set at Open() time 61 info dbconfigs.Connector 62 name string 63 } 64 65 // NewConnectionPool creates a new ConnectionPool. The name is used 66 // to publish stats only. 67 func NewConnectionPool(name string, capacity int, idleTimeout time.Duration, maxLifetime time.Duration, dnsResolutionFrequency time.Duration) *ConnectionPool { 68 cp := &ConnectionPool{name: name, capacity: capacity, idleTimeout: idleTimeout, maxLifetime: maxLifetime, resolutionFrequency: dnsResolutionFrequency} 69 if name == "" || usedNames[name] { 70 return cp 71 } 72 usedNames[name] = true 73 stats.NewGaugeFunc(name+"Capacity", "Connection pool capacity", cp.Capacity) 74 stats.NewGaugeFunc(name+"Available", "Connection pool available", cp.Available) 75 stats.NewGaugeFunc(name+"Active", "Connection pool active", cp.Active) 76 stats.NewGaugeFunc(name+"InUse", "Connection pool in-use", cp.InUse) 77 stats.NewGaugeFunc(name+"MaxCap", "Connection pool max cap", cp.MaxCap) 78 stats.NewCounterFunc(name+"WaitCount", "Connection pool wait count", cp.WaitCount) 79 stats.NewCounterDurationFunc(name+"WaitTime", "Connection pool wait time", cp.WaitTime) 80 stats.NewGaugeDurationFunc(name+"IdleTimeout", "Connection pool idle timeout", cp.IdleTimeout) 81 stats.NewGaugeFunc(name+"IdleClosed", "Connection pool idle closed", cp.IdleClosed) 82 stats.NewGaugeFunc(name+"MaxLifetimeClosed", "Connection pool refresh closed", cp.MaxLifetimeClosed) 83 stats.NewCounterFunc(name+"Exhausted", "Number of times pool had zero available slots", cp.Exhausted) 84 return cp 85 } 86 87 func (cp *ConnectionPool) pool() (p pools.IResourcePool) { 88 cp.mu.Lock() 89 p = cp.connections 90 cp.mu.Unlock() 91 return p 92 } 93 94 // Open must be called before starting to use the pool. 95 // 96 // For instance: 97 // pool := dbconnpool.NewConnectionPool("name", 10, 30*time.Second) 98 // pool.Open(info) 99 // ... 100 // conn, err := pool.Get() 101 // ... 102 func (cp *ConnectionPool) Open(info dbconfigs.Connector) { 103 var refreshCheck pools.RefreshCheck 104 if net.ParseIP(info.Host()) == nil { 105 refreshCheck = netutil.DNSTracker(info.Host()) 106 } else { 107 refreshCheck = nil 108 } 109 cp.mu.Lock() 110 defer cp.mu.Unlock() 111 cp.info = info 112 cp.connections = pools.NewResourcePool(cp.connect, cp.capacity, cp.capacity, cp.idleTimeout, cp.maxLifetime, nil, refreshCheck, cp.resolutionFrequency) 113 } 114 115 // connect is used by the resource pool to create a new Resource. 116 func (cp *ConnectionPool) connect(ctx context.Context) (pools.Resource, error) { 117 c, err := NewDBConnection(ctx, cp.info) 118 if err != nil { 119 return nil, err 120 } 121 return &PooledDBConnection{ 122 DBConnection: c, 123 timeCreated: time.Now(), 124 pool: cp, 125 }, nil 126 } 127 128 // Close will close the pool and wait for connections to be returned before 129 // exiting. 130 func (cp *ConnectionPool) Close() { 131 p := cp.pool() 132 if p == nil { 133 return 134 } 135 // We should not hold the lock while calling Close 136 // because it waits for connections to be returned. 137 p.Close() 138 cp.mu.Lock() 139 cp.connections = nil 140 cp.mu.Unlock() 141 } 142 143 // Get returns a connection. 144 // You must call Recycle on the PooledDBConnection once done. 145 func (cp *ConnectionPool) Get(ctx context.Context) (*PooledDBConnection, error) { 146 p := cp.pool() 147 if p == nil { 148 return nil, ErrConnPoolClosed 149 } 150 r, err := p.Get(ctx, nil) 151 if err != nil { 152 return nil, err 153 } 154 155 return r.(*PooledDBConnection), nil 156 } 157 158 // Put puts a connection into the pool. 159 func (cp *ConnectionPool) Put(conn *PooledDBConnection) { 160 p := cp.pool() 161 if p == nil { 162 panic(ErrConnPoolClosed) 163 } 164 if conn == nil { 165 // conn has a type, if we just Put(conn), we end up 166 // putting an interface with a nil value, that is not 167 // equal to a nil value. So just put a plain nil. 168 p.Put(nil) 169 return 170 } 171 p.Put(conn) 172 } 173 174 // SetCapacity alters the size of the pool at runtime. 175 func (cp *ConnectionPool) SetCapacity(capacity int) (err error) { 176 cp.mu.Lock() 177 defer cp.mu.Unlock() 178 if cp.connections != nil { 179 err = cp.connections.SetCapacity(capacity) 180 if err != nil { 181 return err 182 } 183 } 184 cp.capacity = capacity 185 return nil 186 } 187 188 // SetIdleTimeout sets the idleTimeout on the pool. 189 func (cp *ConnectionPool) SetIdleTimeout(idleTimeout time.Duration) { 190 cp.mu.Lock() 191 defer cp.mu.Unlock() 192 if cp.connections != nil { 193 cp.connections.SetIdleTimeout(idleTimeout) 194 } 195 cp.idleTimeout = idleTimeout 196 } 197 198 // StatsJSON returns the pool stats as a JSOn object. 199 func (cp *ConnectionPool) StatsJSON() string { 200 p := cp.pool() 201 if p == nil { 202 return "{}" 203 } 204 return p.StatsJSON() 205 } 206 207 // Capacity returns the pool capacity. 208 func (cp *ConnectionPool) Capacity() int64 { 209 p := cp.pool() 210 if p == nil { 211 return 0 212 } 213 return p.Capacity() 214 } 215 216 // Available returns the number of available connections in the pool 217 func (cp *ConnectionPool) Available() int64 { 218 p := cp.pool() 219 if p == nil { 220 return 0 221 } 222 return p.Available() 223 } 224 225 // Active returns the number of active connections in the pool 226 func (cp *ConnectionPool) Active() int64 { 227 p := cp.pool() 228 if p == nil { 229 return 0 230 } 231 return p.Active() 232 } 233 234 // InUse returns the number of in-use connections in the pool 235 func (cp *ConnectionPool) InUse() int64 { 236 p := cp.pool() 237 if p == nil { 238 return 0 239 } 240 return p.InUse() 241 } 242 243 // MaxCap returns the maximum size of the pool 244 func (cp *ConnectionPool) MaxCap() int64 { 245 p := cp.pool() 246 if p == nil { 247 return 0 248 } 249 return p.MaxCap() 250 } 251 252 // WaitCount returns how many clients are waiting for a connection 253 func (cp *ConnectionPool) WaitCount() int64 { 254 p := cp.pool() 255 if p == nil { 256 return 0 257 } 258 return p.WaitCount() 259 } 260 261 // WaitTime return the pool WaitTime. 262 func (cp *ConnectionPool) WaitTime() time.Duration { 263 p := cp.pool() 264 if p == nil { 265 return 0 266 } 267 return p.WaitTime() 268 } 269 270 // IdleTimeout returns the idle timeout for the pool. 271 func (cp *ConnectionPool) IdleTimeout() time.Duration { 272 p := cp.pool() 273 if p == nil { 274 return 0 275 } 276 return p.IdleTimeout() 277 } 278 279 // IdleClosed returns the number of connections closed due to idle timeout for the pool. 280 func (cp *ConnectionPool) IdleClosed() int64 { 281 p := cp.pool() 282 if p == nil { 283 return 0 284 } 285 return p.IdleClosed() 286 } 287 288 // MaxLifetimeClosed returns the number of connections closed due to refresh timeout for the pool. 289 func (cp *ConnectionPool) MaxLifetimeClosed() int64 { 290 p := cp.pool() 291 if p == nil { 292 return 0 293 } 294 return p.MaxLifetimeClosed() 295 } 296 297 // Exhausted returns the number of times available went to zero for the pool. 298 func (cp *ConnectionPool) Exhausted() int64 { 299 p := cp.pool() 300 if p == nil { 301 return 0 302 } 303 return p.Exhausted() 304 }