vitess.io/vitess@v0.16.2/go/pools/resource_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 pools provides functionality to manage and reuse resources 18 // like connections. 19 package pools 20 21 import ( 22 "context" 23 "errors" 24 "fmt" 25 "math/rand" 26 "sync" 27 "time" 28 29 "vitess.io/vitess/go/sync2" 30 "vitess.io/vitess/go/timer" 31 "vitess.io/vitess/go/vt/log" 32 "vitess.io/vitess/go/vt/vterrors" 33 34 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 35 ) 36 37 type ( 38 IResourcePool interface { 39 Close() 40 Name() string 41 Get(ctx context.Context, setting *Setting) (resource Resource, err error) 42 Put(resource Resource) 43 SetCapacity(capacity int) error 44 SetIdleTimeout(idleTimeout time.Duration) 45 StatsJSON() string 46 Capacity() int64 47 Available() int64 48 Active() int64 49 InUse() int64 50 MaxCap() int64 51 WaitCount() int64 52 WaitTime() time.Duration 53 IdleTimeout() time.Duration 54 IdleClosed() int64 55 MaxLifetimeClosed() int64 56 Exhausted() int64 57 GetCount() int64 58 GetSettingCount() int64 59 DiffSettingCount() int64 60 ResetSettingCount() int64 61 } 62 63 // Resource defines the interface that every resource must provide. 64 // Thread synchronization between Close() and IsClosed() 65 // is the responsibility of the caller. 66 Resource interface { 67 Close() 68 Expired(time.Duration) bool 69 ApplySetting(ctx context.Context, setting *Setting) error 70 IsSettingApplied() bool 71 IsSameSetting(setting string) bool 72 ResetSetting(ctx context.Context) error 73 } 74 75 // Factory is a function that can be used to create a resource. 76 Factory func(context.Context) (Resource, error) 77 78 resourceWrapper struct { 79 resource Resource 80 timeUsed time.Time 81 } 82 83 // Setting represents a set query and reset query for system settings. 84 Setting struct { 85 query string 86 resetQuery string 87 } 88 89 // ResourcePool allows you to use a pool of resources. 90 ResourcePool struct { 91 // stats. Atomic fields must remain at the top in order to prevent panics on certain architectures. 92 available sync2.AtomicInt64 93 active sync2.AtomicInt64 94 inUse sync2.AtomicInt64 95 waitCount sync2.AtomicInt64 96 waitTime sync2.AtomicDuration 97 idleClosed sync2.AtomicInt64 98 maxLifetimeClosed sync2.AtomicInt64 99 exhausted sync2.AtomicInt64 100 101 capacity sync2.AtomicInt64 102 idleTimeout sync2.AtomicDuration 103 maxLifetime sync2.AtomicDuration 104 105 resources chan resourceWrapper 106 factory Factory 107 idleTimer *timer.Timer 108 logWait func(time.Time) 109 110 settingResources chan resourceWrapper 111 getCount sync2.AtomicInt64 112 getSettingCount sync2.AtomicInt64 113 diffSettingCount sync2.AtomicInt64 114 resetSettingCount sync2.AtomicInt64 115 116 reopenMutex sync.Mutex 117 refresh *poolRefresh 118 } 119 ) 120 121 var ( 122 // ErrClosed is returned if ResourcePool is used when it's closed. 123 ErrClosed = errors.New("resource pool is closed") 124 125 // ErrTimeout is returned if a resource get times out. 126 ErrTimeout = vterrors.New(vtrpcpb.Code_RESOURCE_EXHAUSTED, "resource pool timed out") 127 128 // ErrCtxTimeout is returned if a ctx is already expired by the time the resource pool is used 129 ErrCtxTimeout = vterrors.New(vtrpcpb.Code_DEADLINE_EXCEEDED, "resource pool context already expired") 130 ) 131 132 func NewSetting(query, resetQuery string) *Setting { 133 return &Setting{ 134 query: query, 135 resetQuery: resetQuery, 136 } 137 } 138 139 func (s *Setting) GetQuery() string { 140 return s.query 141 } 142 143 func (s *Setting) GetResetQuery() string { 144 return s.resetQuery 145 } 146 147 // NewResourcePool creates a new ResourcePool pool. 148 // capacity is the number of possible resources in the pool: 149 // there can be up to 'capacity' of these at a given time. 150 // maxCap specifies the extent to which the pool can be resized 151 // in the future through the SetCapacity function. 152 // You cannot resize the pool beyond maxCap. 153 // If a resource is unused beyond idleTimeout, it's replaced 154 // with a new one. 155 // An idleTimeout of 0 means that there is no timeout. 156 // An maxLifetime of 0 means that there is no timeout. 157 // A non-zero value of prefillParallelism causes the pool to be pre-filled. 158 // The value specifies how many resources can be opened in parallel. 159 // refreshCheck is a function we consult at refreshInterval 160 // intervals to determine if the pool should be drained and reopened 161 func NewResourcePool(factory Factory, capacity, maxCap int, idleTimeout time.Duration, maxLifetime time.Duration, logWait func(time.Time), refreshCheck RefreshCheck, refreshInterval time.Duration) *ResourcePool { 162 if capacity <= 0 || maxCap <= 0 || capacity > maxCap { 163 panic(errors.New("invalid/out of range capacity")) 164 } 165 rp := &ResourcePool{ 166 resources: make(chan resourceWrapper, maxCap), 167 settingResources: make(chan resourceWrapper, maxCap), 168 factory: factory, 169 available: sync2.NewAtomicInt64(int64(capacity)), 170 capacity: sync2.NewAtomicInt64(int64(capacity)), 171 idleTimeout: sync2.NewAtomicDuration(idleTimeout), 172 maxLifetime: sync2.NewAtomicDuration(maxLifetime), 173 logWait: logWait, 174 } 175 for i := 0; i < capacity; i++ { 176 rp.resources <- resourceWrapper{} 177 } 178 179 if idleTimeout != 0 { 180 rp.idleTimer = timer.NewTimer(idleTimeout / 10) 181 rp.idleTimer.Start(rp.closeIdleResources) 182 } 183 184 rp.refresh = newPoolRefresh(rp, refreshCheck, refreshInterval) 185 rp.refresh.startRefreshTicker() 186 187 return rp 188 } 189 190 func (rp *ResourcePool) Name() string { 191 return "ResourcePool" 192 } 193 194 // Close empties the pool calling Close on all its resources. 195 // You can call Close while there are outstanding resources. 196 // It waits for all resources to be returned (Put). 197 // After a Close, Get is not allowed. 198 func (rp *ResourcePool) Close() { 199 if rp.idleTimer != nil { 200 rp.idleTimer.Stop() 201 } 202 rp.refresh.stop() 203 _ = rp.SetCapacity(0) 204 } 205 206 // closeIdleResources scans the pool for idle resources 207 func (rp *ResourcePool) closeIdleResources() { 208 available := int(rp.Available()) 209 idleTimeout := rp.IdleTimeout() 210 211 for i := 0; i < available; i++ { 212 var wrapper resourceWrapper 213 var origPool bool 214 select { 215 case wrapper = <-rp.resources: 216 origPool = true 217 case wrapper = <-rp.settingResources: 218 origPool = false 219 default: 220 // stop early if we don't get anything new from the pool 221 return 222 } 223 224 var reopened bool 225 if wrapper.resource != nil && idleTimeout > 0 && time.Until(wrapper.timeUsed.Add(idleTimeout)) < 0 { 226 wrapper.resource.Close() 227 rp.idleClosed.Add(1) 228 rp.reopenResource(&wrapper) 229 reopened = true 230 } 231 rp.returnResource(&wrapper, origPool, reopened) 232 } 233 } 234 235 func (rp *ResourcePool) returnResource(wrapper *resourceWrapper, origPool bool, reopened bool) { 236 if origPool || reopened { 237 rp.resources <- *wrapper 238 } else { 239 rp.settingResources <- *wrapper 240 } 241 } 242 243 // reopen drains and reopens the connection pool 244 func (rp *ResourcePool) reopen() { 245 rp.reopenMutex.Lock() // Avoid race, since we can refresh asynchronously 246 defer rp.reopenMutex.Unlock() 247 capacity := int(rp.capacity.Get()) 248 log.Infof("Draining and reopening resource pool with capacity %d by request", capacity) 249 rp.Close() 250 _ = rp.SetCapacity(capacity) 251 if rp.idleTimer != nil { 252 rp.idleTimer.Start(rp.closeIdleResources) 253 } 254 rp.refresh.startRefreshTicker() 255 } 256 257 // Get will return the next available resource. If capacity 258 // has not been reached, it will create a new one using the factory. Otherwise, 259 // it will wait till the next resource becomes available or a timeout. 260 // A timeout of 0 is an indefinite wait. 261 func (rp *ResourcePool) Get(ctx context.Context, setting *Setting) (resource Resource, err error) { 262 // If ctx has already expired, avoid racing with rp's resource channel. 263 if ctx.Err() != nil { 264 return nil, ErrCtxTimeout 265 } 266 if setting == nil { 267 return rp.get(ctx) 268 } 269 return rp.getWithSettings(ctx, setting) 270 } 271 272 func (rp *ResourcePool) get(ctx context.Context) (resource Resource, err error) { 273 rp.getCount.Add(1) 274 // Fetch 275 var wrapper resourceWrapper 276 var ok bool 277 // If we put both the channel together, then, go select can read from any channel 278 // this way we guarantee it will try to read from the channel we intended to read it from first 279 // and then try to read from next best available resource. 280 select { 281 // check normal resources first 282 case wrapper, ok = <-rp.resources: 283 default: 284 select { 285 // then checking setting resources 286 case wrapper, ok = <-rp.settingResources: 287 default: 288 // now waiting 289 startTime := time.Now() 290 select { 291 case wrapper, ok = <-rp.resources: 292 case wrapper, ok = <-rp.settingResources: 293 case <-ctx.Done(): 294 return nil, ErrTimeout 295 } 296 rp.recordWait(startTime) 297 } 298 } 299 if !ok { 300 return nil, ErrClosed 301 } 302 303 // if the resource has setting applied, we will close it and return a new one 304 if wrapper.resource != nil && wrapper.resource.IsSettingApplied() { 305 rp.resetSettingCount.Add(1) 306 err = wrapper.resource.ResetSetting(ctx) 307 if err != nil { 308 // as reset is unsuccessful, we will close this resource 309 wrapper.resource.Close() 310 wrapper.resource = nil 311 rp.active.Add(-1) 312 } 313 } 314 315 // Unwrap 316 if wrapper.resource == nil { 317 wrapper.resource, err = rp.factory(ctx) 318 if err != nil { 319 rp.resources <- resourceWrapper{} 320 return nil, err 321 } 322 rp.active.Add(1) 323 } 324 if rp.available.Add(-1) <= 0 { 325 rp.exhausted.Add(1) 326 } 327 rp.inUse.Add(1) 328 return wrapper.resource, err 329 } 330 331 func (rp *ResourcePool) getWithSettings(ctx context.Context, setting *Setting) (Resource, error) { 332 rp.getSettingCount.Add(1) 333 var wrapper resourceWrapper 334 var ok bool 335 var err error 336 337 // Fetch 338 select { 339 // check setting resources first 340 case wrapper, ok = <-rp.settingResources: 341 default: 342 select { 343 // then, check normal resources 344 case wrapper, ok = <-rp.resources: 345 default: 346 // now waiting 347 startTime := time.Now() 348 select { 349 case wrapper, ok = <-rp.settingResources: 350 case wrapper, ok = <-rp.resources: 351 case <-ctx.Done(): 352 return nil, ErrTimeout 353 } 354 rp.recordWait(startTime) 355 } 356 } 357 if !ok { 358 return nil, ErrClosed 359 } 360 361 // Checking setting hash id, if it is different, we will close the resource and return a new one later in unwrap 362 if wrapper.resource != nil && wrapper.resource.IsSettingApplied() && !wrapper.resource.IsSameSetting(setting.query) { 363 rp.diffSettingCount.Add(1) 364 err = wrapper.resource.ResetSetting(ctx) 365 if err != nil { 366 // as reset is unsuccessful, we will close this resource 367 wrapper.resource.Close() 368 wrapper.resource = nil 369 rp.active.Add(-1) 370 } 371 } 372 373 // Unwrap 374 if wrapper.resource == nil { 375 wrapper.resource, err = rp.factory(ctx) 376 if err != nil { 377 rp.resources <- resourceWrapper{} 378 return nil, err 379 } 380 rp.active.Add(1) 381 } 382 383 if !wrapper.resource.IsSettingApplied() { 384 if err = wrapper.resource.ApplySetting(ctx, setting); err != nil { 385 // as we are not able to apply setting, we can return this connection to non-setting channel. 386 // TODO: may check the error code to see if it is recoverable or not. 387 rp.resources <- wrapper 388 return nil, err 389 } 390 } 391 392 if rp.available.Add(-1) <= 0 { 393 rp.exhausted.Add(1) 394 } 395 rp.inUse.Add(1) 396 return wrapper.resource, err 397 } 398 399 // Put will return a resource to the pool. For every successful Get, 400 // a corresponding Put is required. If you no longer need a resource, 401 // you will need to call Put(nil) instead of returning the closed resource. 402 // This will cause a new resource to be created in its place. 403 func (rp *ResourcePool) Put(resource Resource) { 404 var wrapper resourceWrapper 405 var recreated bool 406 var hasSettings bool 407 if resource != nil { 408 wrapper = resourceWrapper{ 409 resource: resource, 410 timeUsed: time.Now(), 411 } 412 hasSettings = resource.IsSettingApplied() 413 if resource.Expired(rp.extendedMaxLifetime()) { 414 rp.maxLifetimeClosed.Add(1) 415 resource.Close() 416 resource = nil 417 } 418 } 419 if resource == nil { 420 // Create new resource 421 rp.reopenResource(&wrapper) 422 recreated = true 423 } 424 if !hasSettings || recreated { 425 select { 426 case rp.resources <- wrapper: 427 default: 428 panic(errors.New("attempt to Put into a full ResourcePool")) 429 } 430 } else { 431 select { 432 case rp.settingResources <- wrapper: 433 default: 434 panic(errors.New("attempt to Put into a full ResourcePool")) 435 } 436 } 437 rp.inUse.Add(-1) 438 rp.available.Add(1) 439 } 440 441 func (rp *ResourcePool) reopenResource(wrapper *resourceWrapper) { 442 if r, err := rp.factory(context.TODO()); err == nil { 443 wrapper.resource = r 444 wrapper.timeUsed = time.Now() 445 } else { 446 wrapper.resource = nil 447 rp.active.Add(-1) 448 } 449 } 450 451 // SetCapacity changes the capacity of the pool. 452 // You can use it to shrink or expand, but not beyond 453 // the max capacity. If the change requires the pool 454 // to be shrunk, SetCapacity waits till the necessary 455 // number of resources are returned to the pool. 456 // A SetCapacity of 0 is equivalent to closing the ResourcePool. 457 func (rp *ResourcePool) SetCapacity(capacity int) error { 458 if capacity < 0 || capacity > cap(rp.resources) { 459 return fmt.Errorf("capacity %d is out of range", capacity) 460 } 461 462 // Atomically swap new capacity with old 463 var oldcap int 464 for { 465 oldcap = int(rp.capacity.Get()) 466 if oldcap == 0 && capacity > 0 { 467 // Closed this before, re-open the channel 468 rp.resources = make(chan resourceWrapper, cap(rp.resources)) 469 rp.settingResources = make(chan resourceWrapper, cap(rp.settingResources)) 470 } 471 if oldcap == capacity { 472 return nil 473 } 474 if rp.capacity.CompareAndSwap(int64(oldcap), int64(capacity)) { 475 break 476 } 477 } 478 479 // If the required capacity is less than the current capacity, 480 // then we need to wait till the current resources are returned 481 // to the pool and close them from any of the channel. 482 // Otherwise, if the required capacity is more than the current capacity, 483 // then we just add empty resource to the channel. 484 if capacity < oldcap { 485 for i := 0; i < oldcap-capacity; i++ { 486 var wrapper resourceWrapper 487 select { 488 case wrapper = <-rp.resources: 489 case wrapper = <-rp.settingResources: 490 } 491 if wrapper.resource != nil { 492 wrapper.resource.Close() 493 rp.active.Add(-1) 494 } 495 rp.available.Add(-1) 496 } 497 } else { 498 for i := 0; i < capacity-oldcap; i++ { 499 rp.resources <- resourceWrapper{} 500 rp.available.Add(1) 501 } 502 } 503 if capacity == 0 { 504 close(rp.resources) 505 close(rp.settingResources) 506 } 507 return nil 508 } 509 510 func (rp *ResourcePool) recordWait(start time.Time) { 511 rp.waitCount.Add(1) 512 rp.waitTime.Add(time.Since(start)) 513 if rp.logWait != nil { 514 rp.logWait(start) 515 } 516 } 517 518 // SetIdleTimeout sets the idle timeout. It can only be used if there was an 519 // idle timeout set when the pool was created. 520 func (rp *ResourcePool) SetIdleTimeout(idleTimeout time.Duration) { 521 if rp.idleTimer == nil { 522 panic("SetIdleTimeout called when timer not initialized") 523 } 524 525 rp.idleTimeout.Set(idleTimeout) 526 rp.idleTimer.SetInterval(idleTimeout / 10) 527 } 528 529 // StatsJSON returns the stats in JSON format. 530 func (rp *ResourcePool) StatsJSON() string { 531 return fmt.Sprintf(`{"Capacity": %v, "Available": %v, "Active": %v, "InUse": %v, "MaxCapacity": %v, "WaitCount": %v, "WaitTime": %v, "IdleTimeout": %v, "IdleClosed": %v, "MaxLifetimeClosed": %v, "Exhausted": %v}`, 532 rp.Capacity(), 533 rp.Available(), 534 rp.Active(), 535 rp.InUse(), 536 rp.MaxCap(), 537 rp.WaitCount(), 538 rp.WaitTime().Nanoseconds(), 539 rp.IdleTimeout().Nanoseconds(), 540 rp.IdleClosed(), 541 rp.MaxLifetimeClosed(), 542 rp.Exhausted(), 543 ) 544 } 545 546 // Capacity returns the capacity. 547 func (rp *ResourcePool) Capacity() int64 { 548 return rp.capacity.Get() 549 } 550 551 // Available returns the number of currently unused and available resources. 552 func (rp *ResourcePool) Available() int64 { 553 return rp.available.Get() 554 } 555 556 // Active returns the number of active (i.e. non-nil) resources either in the 557 // pool or claimed for use 558 func (rp *ResourcePool) Active() int64 { 559 return rp.active.Get() 560 } 561 562 // InUse returns the number of claimed resources from the pool 563 func (rp *ResourcePool) InUse() int64 { 564 return rp.inUse.Get() 565 } 566 567 // MaxCap returns the max capacity. 568 func (rp *ResourcePool) MaxCap() int64 { 569 return int64(cap(rp.resources)) 570 } 571 572 // WaitCount returns the total number of waits. 573 func (rp *ResourcePool) WaitCount() int64 { 574 return rp.waitCount.Get() 575 } 576 577 // WaitTime returns the total wait time. 578 func (rp *ResourcePool) WaitTime() time.Duration { 579 return rp.waitTime.Get() 580 } 581 582 // IdleTimeout returns the resource idle timeout. 583 func (rp *ResourcePool) IdleTimeout() time.Duration { 584 return rp.idleTimeout.Get() 585 } 586 587 // IdleClosed returns the count of resources closed due to idle timeout. 588 func (rp *ResourcePool) IdleClosed() int64 { 589 return rp.idleClosed.Get() 590 } 591 592 // extendedLifetimeTimeout returns random duration within range [maxLifetime, 2*maxLifetime) 593 func (rp *ResourcePool) extendedMaxLifetime() time.Duration { 594 maxLifetime := rp.maxLifetime.Get() 595 if maxLifetime == 0 { 596 return 0 597 } 598 return maxLifetime + time.Duration(rand.Int63n(maxLifetime.Nanoseconds())) 599 } 600 601 // MaxLifetimeClosed returns the count of resources closed due to refresh timeout. 602 func (rp *ResourcePool) MaxLifetimeClosed() int64 { 603 return rp.maxLifetimeClosed.Get() 604 } 605 606 // Exhausted returns the number of times Available dropped below 1 607 func (rp *ResourcePool) Exhausted() int64 { 608 return rp.exhausted.Get() 609 } 610 611 // GetCount returns the number of times get was called 612 func (rp *ResourcePool) GetCount() int64 { 613 return rp.getCount.Get() 614 } 615 616 // GetSettingCount returns the number of times getWithSettings was called 617 func (rp *ResourcePool) GetSettingCount() int64 { 618 return rp.getSettingCount.Get() 619 } 620 621 // DiffSettingCount returns the number of times different setting were applied on the resource. 622 func (rp *ResourcePool) DiffSettingCount() int64 { 623 return rp.diffSettingCount.Get() 624 } 625 626 // ResetSettingCount returns the number of times setting were reset on the resource. 627 func (rp *ResourcePool) ResetSettingCount() int64 { 628 return rp.resetSettingCount.Get() 629 }