|
|
- // Copyright 2012, Google Inc. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
-
- package pools
-
- import (
- "fmt"
- "sync"
- "time"
- )
-
- // RoundRobin is deprecated. Use ResourcePool instead.
- // RoundRobin allows you to use a pool of resources in a round robin fashion.
- type RoundRobin struct {
- mu sync.Mutex
- available *sync.Cond
- resources chan fifoWrapper
- size int64
- factory Factory
- idleTimeout time.Duration
-
- // stats
- waitCount int64
- waitTime time.Duration
- }
-
- type fifoWrapper struct {
- resource Resource
- timeUsed time.Time
- }
-
- // NewRoundRobin creates a new RoundRobin pool.
- // capacity is the maximum number of resources RoundRobin will create.
- // factory will be the function used to create resources.
- // If a resource is unused beyond idleTimeout, it's discarded.
- func NewRoundRobin(capacity int, idleTimeout time.Duration) *RoundRobin {
- r := &RoundRobin{
- resources: make(chan fifoWrapper, capacity),
- size: 0,
- idleTimeout: idleTimeout,
- }
- r.available = sync.NewCond(&r.mu)
- return r
- }
-
- // Open starts allowing the creation of resources
- func (rr *RoundRobin) Open(factory Factory) {
- rr.mu.Lock()
- defer rr.mu.Unlock()
- rr.factory = factory
- }
-
- // Close empties the pool calling Close on all its resources.
- // It waits for all resources to be returned (Put).
- func (rr *RoundRobin) Close() {
- rr.mu.Lock()
- defer rr.mu.Unlock()
- for rr.size > 0 {
- select {
- case fw := <-rr.resources:
- go fw.resource.Close()
- rr.size--
- default:
- rr.available.Wait()
- }
- }
- rr.factory = nil
- }
-
- func (rr *RoundRobin) IsClosed() bool {
- return rr.factory == nil
- }
-
- // Get will return the next available resource. If none is available, and capacity
- // has not been reached, it will create a new one using the factory. Otherwise,
- // it will indefinitely wait till the next resource becomes available.
- func (rr *RoundRobin) Get() (resource Resource, err error) {
- return rr.get(true)
- }
-
- // TryGet will return the next available resource. If none is available, and capacity
- // has not been reached, it will create a new one using the factory. Otherwise,
- // it will return nil with no error.
- func (rr *RoundRobin) TryGet() (resource Resource, err error) {
- return rr.get(false)
- }
-
- func (rr *RoundRobin) get(wait bool) (resource Resource, err error) {
- rr.mu.Lock()
- defer rr.mu.Unlock()
- // Any waits in this loop will release the lock, and it will be
- // reacquired before the waits return.
- for {
- select {
- case fw := <-rr.resources:
- // Found a free resource in the channel
- if rr.idleTimeout > 0 && fw.timeUsed.Add(rr.idleTimeout).Sub(time.Now()) < 0 {
- // resource has been idle for too long. Discard & go for next.
- go fw.resource.Close()
- rr.size--
- // Nobody else should be waiting, but signal anyway.
- rr.available.Signal()
- continue
- }
- return fw.resource, nil
- default:
- // resource channel is empty
- if rr.size >= int64(cap(rr.resources)) {
- // The pool is full
- if wait {
- start := time.Now()
- rr.available.Wait()
- rr.recordWait(start)
- continue
- }
- return nil, nil
- }
- // Pool is not full. Create a resource.
- if resource, err = rr.waitForCreate(); err != nil {
- // size was decremented, and somebody could be waiting.
- rr.available.Signal()
- return nil, err
- }
- // Creation successful. Account for this by incrementing size.
- rr.size++
- return resource, err
- }
- }
- }
-
- func (rr *RoundRobin) recordWait(start time.Time) {
- rr.waitCount++
- rr.waitTime += time.Now().Sub(start)
- }
-
- func (rr *RoundRobin) waitForCreate() (resource Resource, err error) {
- // Prevent thundering herd: increment size before creating resource, and decrement after.
- rr.size++
- rr.mu.Unlock()
- defer func() {
- rr.mu.Lock()
- rr.size--
- }()
- return rr.factory()
- }
-
- // Put will return a resource to the pool. You MUST return every resource to the pool,
- // even if it's closed. If a resource is closed, you should call Put(nil).
- func (rr *RoundRobin) Put(resource Resource) {
- rr.mu.Lock()
- defer rr.available.Signal()
- defer rr.mu.Unlock()
-
- if rr.size > int64(cap(rr.resources)) {
- if resource != nil {
- go resource.Close()
- }
- rr.size--
- } else if resource == nil {
- rr.size--
- } else {
- if len(rr.resources) == cap(rr.resources) {
- panic("unexpected")
- }
- rr.resources <- fifoWrapper{resource, time.Now()}
- }
- }
-
- // Set capacity changes the capacity of the pool.
- // You can use it to expand or shrink.
- func (rr *RoundRobin) SetCapacity(capacity int) error {
- rr.mu.Lock()
- defer rr.available.Broadcast()
- defer rr.mu.Unlock()
-
- nr := make(chan fifoWrapper, capacity)
- // This loop transfers resources from the old channel
- // to the new one, until it fills up or runs out.
- // It discards extras, if any.
- for {
- select {
- case fw := <-rr.resources:
- if len(nr) < cap(nr) {
- nr <- fw
- } else {
- go fw.resource.Close()
- rr.size--
- }
- continue
- default:
- }
- break
- }
- rr.resources = nr
- return nil
- }
-
- func (rr *RoundRobin) SetIdleTimeout(idleTimeout time.Duration) {
- rr.mu.Lock()
- defer rr.mu.Unlock()
- rr.idleTimeout = idleTimeout
- }
-
- func (rr *RoundRobin) StatsJSON() string {
- s, c, a, wc, wt, it := rr.Stats()
- return fmt.Sprintf("{\"Size\": %v, \"Capacity\": %v, \"Available\": %v, \"WaitCount\": %v, \"WaitTime\": %v, \"IdleTimeout\": %v}", s, c, a, wc, int64(wt), int64(it))
- }
-
- func (rr *RoundRobin) Stats() (size, capacity, available, waitCount int64, waitTime, idleTimeout time.Duration) {
- rr.mu.Lock()
- defer rr.mu.Unlock()
- return rr.size, int64(cap(rr.resources)), int64(len(rr.resources)), rr.waitCount, rr.waitTime, rr.idleTimeout
- }
|