vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletserver/connpool/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 package connpool 18 19 import ( 20 "context" 21 "fmt" 22 "net" 23 "strings" 24 "sync" 25 "time" 26 27 "vitess.io/vitess/go/netutil" 28 "vitess.io/vitess/go/pools" 29 "vitess.io/vitess/go/sync2" 30 "vitess.io/vitess/go/trace" 31 "vitess.io/vitess/go/vt/callerid" 32 "vitess.io/vitess/go/vt/dbconfigs" 33 "vitess.io/vitess/go/vt/dbconnpool" 34 "vitess.io/vitess/go/vt/log" 35 "vitess.io/vitess/go/vt/mysqlctl" 36 "vitess.io/vitess/go/vt/servenv" 37 "vitess.io/vitess/go/vt/vterrors" 38 "vitess.io/vitess/go/vt/vttablet/tabletserver/tabletenv" 39 40 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 41 ) 42 43 // ErrConnPoolClosed is returned when the connection pool is closed. 44 var ErrConnPoolClosed = vterrors.New(vtrpcpb.Code_INTERNAL, "internal error: unexpected: conn pool is closed") 45 46 const ( 47 getWithoutS = "GetWithoutSettings" 48 getWithS = "GetWithSettings" 49 ) 50 51 // Pool implements a custom connection pool for tabletserver. 52 // It's similar to dbconnpool.ConnPool, but the connections it creates 53 // come with built-in ability to kill in-flight queries. These connections 54 // also trigger a CheckMySQL call if we fail to connect to MySQL. 55 // Other than the connection type, ConnPool maintains an additional 56 // pool of dba connections that are used to kill connections. 57 type Pool struct { 58 env tabletenv.Env 59 name string 60 mu sync.Mutex 61 connections pools.IResourcePool 62 capacity int 63 prefillParallelism int 64 timeout time.Duration 65 idleTimeout time.Duration 66 maxLifetime time.Duration 67 waiterCap int64 68 waiterCount sync2.AtomicInt64 69 waiterQueueFull sync2.AtomicInt64 70 dbaPool *dbconnpool.ConnectionPool 71 appDebugParams dbconfigs.Connector 72 getConnTime *servenv.TimingsWrapper 73 } 74 75 // NewPool creates a new Pool. The name is used 76 // to publish stats only. 77 func NewPool(env tabletenv.Env, name string, cfg tabletenv.ConnPoolConfig) *Pool { 78 idleTimeout := cfg.IdleTimeoutSeconds.Get() 79 maxLifetime := cfg.MaxLifetimeSeconds.Get() 80 cp := &Pool{ 81 env: env, 82 name: name, 83 capacity: cfg.Size, 84 prefillParallelism: cfg.PrefillParallelism, 85 timeout: cfg.TimeoutSeconds.Get(), 86 idleTimeout: idleTimeout, 87 maxLifetime: maxLifetime, 88 waiterCap: int64(cfg.MaxWaiters), 89 dbaPool: dbconnpool.NewConnectionPool("", 1, idleTimeout, maxLifetime, 0), 90 } 91 if name == "" { 92 return cp 93 } 94 env.Exporter().NewGaugeFunc(name+"Capacity", "Tablet server conn pool capacity", cp.Capacity) 95 env.Exporter().NewGaugeFunc(name+"Available", "Tablet server conn pool available", cp.Available) 96 env.Exporter().NewGaugeFunc(name+"Active", "Tablet server conn pool active", cp.Active) 97 env.Exporter().NewGaugeFunc(name+"InUse", "Tablet server conn pool in use", cp.InUse) 98 env.Exporter().NewGaugeFunc(name+"MaxCap", "Tablet server conn pool max cap", cp.MaxCap) 99 env.Exporter().NewCounterFunc(name+"WaitCount", "Tablet server conn pool wait count", cp.WaitCount) 100 env.Exporter().NewCounterDurationFunc(name+"WaitTime", "Tablet server wait time", cp.WaitTime) 101 env.Exporter().NewGaugeDurationFunc(name+"IdleTimeout", "Tablet server idle timeout", cp.IdleTimeout) 102 env.Exporter().NewCounterFunc(name+"IdleClosed", "Tablet server conn pool idle closed", cp.IdleClosed) 103 env.Exporter().NewCounterFunc(name+"MaxLifetimeClosed", "Tablet server conn pool refresh closed", cp.MaxLifetimeClosed) 104 env.Exporter().NewCounterFunc(name+"Exhausted", "Number of times pool had zero available slots", cp.Exhausted) 105 env.Exporter().NewCounterFunc(name+"WaiterQueueFull", "Number of times the waiter queue was full", cp.waiterQueueFull.Get) 106 env.Exporter().NewCounterFunc(name+"Get", "Tablet server conn pool get count", cp.GetCount) 107 env.Exporter().NewCounterFunc(name+"GetSetting", "Tablet server conn pool get with setting count", cp.GetSettingCount) 108 env.Exporter().NewCounterFunc(name+"DiffSetting", "Number of times pool applied different setting", cp.DiffSettingCount) 109 env.Exporter().NewCounterFunc(name+"ResetSetting", "Number of times pool reset the setting", cp.ResetSettingCount) 110 cp.getConnTime = env.Exporter().NewTimings(name+"GetConnTime", "Tracks the amount of time it takes to get a connection", "Settings") 111 112 return cp 113 } 114 115 func (cp *Pool) pool() (p pools.IResourcePool) { 116 cp.mu.Lock() 117 p = cp.connections 118 cp.mu.Unlock() 119 return p 120 } 121 122 // Open must be called before starting to use the pool. 123 func (cp *Pool) Open(appParams, dbaParams, appDebugParams dbconfigs.Connector) { 124 cp.mu.Lock() 125 defer cp.mu.Unlock() 126 127 if cp.prefillParallelism != 0 { 128 log.Infof("Opening pool: '%s'", cp.name) 129 defer log.Infof("Done opening pool: '%s'", cp.name) 130 } 131 132 f := func(ctx context.Context) (pools.Resource, error) { 133 return NewDBConn(ctx, cp, appParams) 134 } 135 136 var refreshCheck pools.RefreshCheck 137 if net.ParseIP(appParams.Host()) == nil { 138 refreshCheck = netutil.DNSTracker(appParams.Host()) 139 } 140 141 cp.connections = pools.NewResourcePool(f, cp.capacity, cp.capacity, cp.idleTimeout, cp.maxLifetime, cp.getLogWaitCallback(), refreshCheck, mysqlctl.PoolDynamicHostnameResolution) 142 cp.appDebugParams = appDebugParams 143 144 cp.dbaPool.Open(dbaParams) 145 } 146 147 func (cp *Pool) getLogWaitCallback() func(time.Time) { 148 if cp.name == "" { 149 return func(start time.Time) {} // no op 150 } 151 return func(start time.Time) { 152 cp.env.Stats().WaitTimings.Record(cp.name+"ResourceWaitTime", start) 153 } 154 } 155 156 // Close will close the pool and wait for connections to be returned before 157 // exiting. 158 func (cp *Pool) Close() { 159 log.Infof("connpool - started execution of Close") 160 p := cp.pool() 161 log.Infof("connpool - found the pool") 162 if p == nil { 163 log.Infof("connpool - pool is empty") 164 return 165 } 166 // We should not hold the lock while calling Close 167 // because it waits for connections to be returned. 168 log.Infof("connpool - calling close on the pool") 169 p.Close() 170 log.Infof("connpool - acquiring lock") 171 cp.mu.Lock() 172 log.Infof("connpool - acquired lock") 173 cp.connections = nil 174 cp.mu.Unlock() 175 log.Infof("connpool - closing dbaPool") 176 cp.dbaPool.Close() 177 log.Infof("connpool - finished execution of Close") 178 } 179 180 // Get returns a connection. 181 // You must call Recycle on DBConn once done. 182 func (cp *Pool) Get(ctx context.Context, setting *pools.Setting) (*DBConn, error) { 183 span, ctx := trace.NewSpan(ctx, "Pool.Get") 184 defer span.Finish() 185 186 if cp.waiterCap > 0 { 187 waiterCount := cp.waiterCount.Add(1) 188 defer cp.waiterCount.Add(-1) 189 if waiterCount > cp.waiterCap { 190 cp.waiterQueueFull.Add(1) 191 return nil, vterrors.Errorf(vtrpcpb.Code_RESOURCE_EXHAUSTED, "pool %s waiter count exceeded", cp.name) 192 } 193 } 194 195 if cp.isCallerIDAppDebug(ctx) { 196 return NewDBConnNoPool(ctx, cp.appDebugParams, cp.dbaPool, setting) 197 } 198 p := cp.pool() 199 if p == nil { 200 return nil, ErrConnPoolClosed 201 } 202 span.Annotate("capacity", p.Capacity()) 203 span.Annotate("in_use", p.InUse()) 204 span.Annotate("available", p.Available()) 205 span.Annotate("active", p.Active()) 206 207 if cp.timeout != 0 { 208 var cancel context.CancelFunc 209 ctx, cancel = context.WithTimeout(ctx, cp.timeout) 210 defer cancel() 211 } 212 213 start := time.Now() 214 r, err := p.Get(ctx, setting) 215 if err != nil { 216 return nil, err 217 } 218 if cp.getConnTime != nil { 219 if setting == nil { 220 cp.getConnTime.Record(getWithoutS, start) 221 } else { 222 cp.getConnTime.Record(getWithS, start) 223 } 224 } 225 return r.(*DBConn), nil 226 } 227 228 // Put puts a connection into the pool. 229 func (cp *Pool) Put(conn *DBConn) { 230 p := cp.pool() 231 if p == nil { 232 panic(ErrConnPoolClosed) 233 } 234 if conn == nil { 235 p.Put(nil) 236 } else { 237 p.Put(conn) 238 } 239 } 240 241 // SetCapacity alters the size of the pool at runtime. 242 func (cp *Pool) SetCapacity(capacity int) (err error) { 243 cp.mu.Lock() 244 defer cp.mu.Unlock() 245 if cp.connections != nil { 246 err = cp.connections.SetCapacity(capacity) 247 if err != nil { 248 return err 249 } 250 } 251 cp.capacity = capacity 252 return nil 253 } 254 255 // SetIdleTimeout sets the idleTimeout on the pool. 256 func (cp *Pool) SetIdleTimeout(idleTimeout time.Duration) { 257 cp.mu.Lock() 258 defer cp.mu.Unlock() 259 if cp.connections != nil { 260 cp.connections.SetIdleTimeout(idleTimeout) 261 } 262 cp.dbaPool.SetIdleTimeout(idleTimeout) 263 cp.idleTimeout = idleTimeout 264 } 265 266 // StatsJSON returns the pool stats as a JSON object. 267 func (cp *Pool) StatsJSON() string { 268 p := cp.pool() 269 if p == nil { 270 return "{}" 271 } 272 res := p.StatsJSON() 273 closingBraceIndex := strings.LastIndex(res, "}") 274 if closingBraceIndex == -1 { // unexpected... 275 return res 276 } 277 return fmt.Sprintf(`%s, "WaiterQueueFull": %v}`, res[:closingBraceIndex], cp.waiterQueueFull.Get()) 278 } 279 280 // Capacity returns the pool capacity. 281 func (cp *Pool) Capacity() int64 { 282 p := cp.pool() 283 if p == nil { 284 return 0 285 } 286 return p.Capacity() 287 } 288 289 // Available returns the number of available connections in the pool 290 func (cp *Pool) Available() int64 { 291 p := cp.pool() 292 if p == nil { 293 return 0 294 } 295 return p.Available() 296 } 297 298 // Active returns the number of active connections in the pool 299 func (cp *Pool) Active() int64 { 300 p := cp.pool() 301 if p == nil { 302 return 0 303 } 304 return p.Active() 305 } 306 307 // InUse returns the number of in-use connections in the pool 308 func (cp *Pool) InUse() int64 { 309 p := cp.pool() 310 if p == nil { 311 return 0 312 } 313 return p.InUse() 314 } 315 316 // MaxCap returns the maximum size of the pool 317 func (cp *Pool) MaxCap() int64 { 318 p := cp.pool() 319 if p == nil { 320 return 0 321 } 322 return p.MaxCap() 323 } 324 325 // WaitCount returns how many clients are waiting for a connection 326 func (cp *Pool) WaitCount() int64 { 327 p := cp.pool() 328 if p == nil { 329 return 0 330 } 331 return p.WaitCount() 332 } 333 334 // WaitTime return the pool WaitTime. 335 func (cp *Pool) WaitTime() time.Duration { 336 p := cp.pool() 337 if p == nil { 338 return 0 339 } 340 return p.WaitTime() 341 } 342 343 // IdleTimeout returns the idle timeout for the pool. 344 func (cp *Pool) IdleTimeout() time.Duration { 345 p := cp.pool() 346 if p == nil { 347 return 0 348 } 349 return p.IdleTimeout() 350 } 351 352 // IdleClosed returns the number of closed connections for the pool. 353 func (cp *Pool) IdleClosed() int64 { 354 p := cp.pool() 355 if p == nil { 356 return 0 357 } 358 return p.IdleClosed() 359 } 360 361 // MaxLifetimeClosed returns the number of connections closed to refresh timeout for the pool. 362 func (cp *Pool) MaxLifetimeClosed() int64 { 363 p := cp.pool() 364 if p == nil { 365 return 0 366 } 367 return p.MaxLifetimeClosed() 368 } 369 370 // Exhausted returns the number of times available went to zero for the pool. 371 func (cp *Pool) Exhausted() int64 { 372 p := cp.pool() 373 if p == nil { 374 return 0 375 } 376 return p.Exhausted() 377 } 378 379 // GetCount returns the number of times get was called 380 func (cp *Pool) GetCount() int64 { 381 p := cp.pool() 382 if p == nil { 383 return 0 384 } 385 return p.GetCount() 386 } 387 388 // GetSettingCount returns the number of times getWithSettings was called 389 func (cp *Pool) GetSettingCount() int64 { 390 p := cp.pool() 391 if p == nil { 392 return 0 393 } 394 return p.GetSettingCount() 395 } 396 397 // DiffSettingCount returns the number of times different settings were applied on the resource. 398 func (cp *Pool) DiffSettingCount() int64 { 399 p := cp.pool() 400 if p == nil { 401 return 0 402 } 403 return p.DiffSettingCount() 404 } 405 406 // ResetSettingCount returns the number of times settings were reset on the resource. 407 func (cp *Pool) ResetSettingCount() int64 { 408 p := cp.pool() 409 if p == nil { 410 return 0 411 } 412 return p.ResetSettingCount() 413 } 414 415 func (cp *Pool) isCallerIDAppDebug(ctx context.Context) bool { 416 params, err := cp.appDebugParams.MysqlParams() 417 if err != nil { 418 return false 419 } 420 if params == nil || params.Uname == "" { 421 return false 422 } 423 callerID := callerid.ImmediateCallerIDFromContext(ctx) 424 return callerID != nil && callerID.Username == params.Uname 425 }