Class: Stud::Pool

Inherits:
Object
  • Object
show all
Defined in:
lib/stud/pool.rb

Overview

Public: A thread-safe, generic resource pool.

This class is agnostic as to the resources in the pool. You can put database connections, sockets, threads, etc. It’s up to you!

Examples:

pool = Pool.new
pool.add(Sequel.connect("postgres://pg-readonly-1/prod"))
pool.add(Sequel.connect("postgres://pg-readonly-2/prod"))
pool.add(Sequel.connect("postgres://pg-readonly-3/prod"))

pool.fetch # =>  Returns one of the Sequel::Database values from the pool

Defined Under Namespace

Classes: Error, InvalidAction, NotFound, ResourceBusy

Instance Method Summary collapse

Constructor Details

#initialize(max_size = nil) ⇒ Pool

Public: initialize a new pool.

max_size - if specified, limits the number of resources allowed in the pool.



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/stud/pool.rb', line 37

def initialize(max_size=nil)
  # Available resources
  @available = Hash.new
  # Busy resources
  @busy = Hash.new

  # The pool lock
  @lock = Mutex.new

  # Locks for blocking {#fetch} calls if the pool is full.
  @full_lock = Mutex.new
  @full_cv = ConditionVariable.new

  # Maximum size of this pool.
  @max_size = max_size
end

Instance Method Details

#add(resource) ⇒ Object

Public: Add a new resource to this pool.

The resource, once added, is assumed to be available for use. That means once you add it, you must not use it unless you receive it from #fetch

resource - the object resource to add to the pool.

Returns nothing



84
85
86
87
88
89
# File 'lib/stud/pool.rb', line 84

def add(resource)
  @lock.synchronize do
    @available[resource.object_id] = resource
  end
  return nil
end

#countObject

Public: the count of resources in the pool

Returns the count of resources in the pool.



71
72
73
# File 'lib/stud/pool.rb', line 71

def count
  return (@busy.size + @available.size)
end

#fetch(&default_value_block) ⇒ Object

Public: Fetch an available resource.

If no resource is available, and the pool is not full, the default_value_block will be called and the return value of it used as the resource.

If no resource is availabe, and the pool is full, this call will block until a resource is available.

Returns a resource ready to be used.



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/stud/pool.rb', line 101

def fetch(&default_value_block)
  @lock.synchronize do
    object_id, resource = @available.shift
    if !resource.nil?
      @busy[resource.object_id] = resource
      return resource
    end
  end

  @full_lock.synchronize do
    if full?
      # This should really use a logger.
      puts "=> Pool is full and nothing available. Waiting for a release..."
      @full_cv.wait(@full_lock)
      return fetch(&default_value_block)
    end
  end

  # TODO(sissel): If no block is given, we should block until a resource is
  # available.

  # If we get here, no resource is available and the pool is not full.
  resource = default_value_block.call
  # Only add the resource if the default_value_block returned one.
  if !resource.nil?
    add(resource)
    return fetch
  end
end

#release(resource) ⇒ Object

Public: Release this resource back to the pool.

After you finish using a resource you received with #fetch, you must release it back to the pool using this method.

Alternately, you can #remove it if you want to remove it from the pool instead of releasing it.



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/stud/pool.rb', line 197

def release(resource)
  @lock.synchronize do
    if !include?(resource)
      raise NotFound, "No resource, #{resource.inspect}, found in pool"
    end

    # Release is a no-op if this resource is already available.
    #return if available?(resource)
    @busy.delete(resource.object_id)
    @available[resource.object_id] = resource

    # Notify any threads waiting on a resource from the pool.
    @full_lock.synchronize { @full_cv.signal }
  end
end

#remove(resource) ⇒ Object

Public: Remove a resource from the pool.

This is useful if the resource is no longer useful. For example, if it is a database connection and that connection has failed.

This resource MUST be available and not busy.

Raises Pool::NotFound if no such resource is found. Raises Pool::ResourceBusy if the resource is found but in use.



140
141
142
143
144
145
146
147
148
149
150
# File 'lib/stud/pool.rb', line 140

def remove(resource)
  # Find the object by object_id
  #p [:internal, :busy => @busy, :available => @available]
  @lock.synchronize do
    if available?(resource)
      raise InvalidAction, "This resource must be busy for you to remove " \
        "it (ie; it must be fetched from the pool)"
    end
    @busy.delete(resource.object_id)
  end
end

#sized?Boolean

Private: Is this pool size-limited?

Returns true if this pool was created with a max_size. False, otherwise.

Returns:

  • (Boolean)


57
58
59
# File 'lib/stud/pool.rb', line 57

def sized?
  return !@max_size.nil?
end