This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
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
|
||||
Reference in New Issue
Block a user