152 lines
4.1 KiB
Ruby
152 lines
4.1 KiB
Ruby
|
module EventMachine
|
||
|
# A simple async resource pool based on a resource and work queue. Resources
|
||
|
# are enqueued and work waits for resources to become available.
|
||
|
#
|
||
|
# @example
|
||
|
# require 'em-http-request'
|
||
|
#
|
||
|
# EM.run do
|
||
|
# pool = EM::Pool.new
|
||
|
# spawn = lambda { pool.add EM::HttpRequest.new('http://example.org') }
|
||
|
# 10.times { spawn[] }
|
||
|
# done, scheduled = 0, 0
|
||
|
#
|
||
|
# check = lambda do
|
||
|
# done += 1
|
||
|
# if done >= scheduled
|
||
|
# EM.stop
|
||
|
# end
|
||
|
# end
|
||
|
#
|
||
|
# pool.on_error { |conn| spawn[] }
|
||
|
#
|
||
|
# 100.times do |i|
|
||
|
# scheduled += 1
|
||
|
# pool.perform do |conn|
|
||
|
# req = conn.get :path => '/', :keepalive => true
|
||
|
#
|
||
|
# req.callback do
|
||
|
# p [:success, conn.object_id, i, req.response.size]
|
||
|
# check[]
|
||
|
# end
|
||
|
#
|
||
|
# req.errback { check[] }
|
||
|
#
|
||
|
# req
|
||
|
# end
|
||
|
# end
|
||
|
# end
|
||
|
#
|
||
|
# Resources are expected to be controlled by an object responding to a
|
||
|
# deferrable/completion style API with callback and errback blocks.
|
||
|
#
|
||
|
class Pool
|
||
|
|
||
|
def initialize
|
||
|
@resources = EM::Queue.new
|
||
|
@removed = []
|
||
|
@contents = []
|
||
|
@on_error = nil
|
||
|
end
|
||
|
|
||
|
def add resource
|
||
|
@contents << resource
|
||
|
requeue resource
|
||
|
end
|
||
|
|
||
|
def remove resource
|
||
|
@contents.delete resource
|
||
|
@removed << resource
|
||
|
end
|
||
|
|
||
|
# Returns a list for introspection purposes only. You should *NEVER* call
|
||
|
# modification or work oriented methods on objects in this list. A good
|
||
|
# example use case is periodic statistics collection against a set of
|
||
|
# connection resources.
|
||
|
#
|
||
|
# @example
|
||
|
# pool.contents.inject(0) { |sum, connection| connection.num_bytes }
|
||
|
def contents
|
||
|
@contents.dup
|
||
|
end
|
||
|
|
||
|
# Define a default catch-all for when the deferrables returned by work
|
||
|
# blocks enter a failed state. By default all that happens is that the
|
||
|
# resource is returned to the pool. If on_error is defined, this block is
|
||
|
# responsible for re-adding the resource to the pool if it is still usable.
|
||
|
# In other words, it is generally assumed that on_error blocks explicitly
|
||
|
# handle the rest of the lifetime of the resource.
|
||
|
def on_error *a, &b
|
||
|
@on_error = EM::Callback(*a, &b)
|
||
|
end
|
||
|
|
||
|
# Perform a given #call-able object or block. The callable object will be
|
||
|
# called with a resource from the pool as soon as one is available, and is
|
||
|
# expected to return a deferrable.
|
||
|
#
|
||
|
# The deferrable will have callback and errback added such that when the
|
||
|
# deferrable enters a finished state, the object is returned to the pool.
|
||
|
#
|
||
|
# If on_error is defined, then objects are not automatically returned to the
|
||
|
# pool.
|
||
|
def perform(*a, &b)
|
||
|
work = EM::Callback(*a, &b)
|
||
|
|
||
|
@resources.pop do |resource|
|
||
|
if removed? resource
|
||
|
@removed.delete resource
|
||
|
reschedule work
|
||
|
else
|
||
|
process work, resource
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
alias reschedule perform
|
||
|
|
||
|
# A peek at the number of enqueued jobs waiting for resources
|
||
|
def num_waiting
|
||
|
@resources.num_waiting
|
||
|
end
|
||
|
|
||
|
# Removed will show resources in a partial pruned state. Resources in the
|
||
|
# removed list may not appear in the contents list if they are currently in
|
||
|
# use.
|
||
|
def removed? resource
|
||
|
@removed.include? resource
|
||
|
end
|
||
|
|
||
|
protected
|
||
|
def requeue resource
|
||
|
@resources.push resource
|
||
|
end
|
||
|
|
||
|
def failure resource
|
||
|
if @on_error
|
||
|
@contents.delete resource
|
||
|
@on_error.call resource
|
||
|
# Prevent users from calling a leak.
|
||
|
@removed.delete resource
|
||
|
else
|
||
|
requeue resource
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def completion deferrable, resource
|
||
|
deferrable.callback { requeue resource }
|
||
|
deferrable.errback { failure resource }
|
||
|
end
|
||
|
|
||
|
def process work, resource
|
||
|
deferrable = work.call resource
|
||
|
if deferrable.kind_of?(EM::Deferrable)
|
||
|
completion deferrable, resource
|
||
|
else
|
||
|
raise ArgumentError, "deferrable expected from work"
|
||
|
end
|
||
|
rescue
|
||
|
failure resource
|
||
|
raise
|
||
|
end
|
||
|
end
|
||
|
end
|