class Async::HTTP::Pool
#reusable? -> can be used again.
#multiplex -> 1 or more.
Resources must respond to
This pool doesn’t impose a maximum number of open resources, but it WILL block if there are no available resources and trying to allocate another one fails.
In general we don’t know the policy until connection is established.
- Multiplex requests per connection (HTTP2)
- Multiple sequential requests per connection (HTTP1 with keep-alive)
- Single request per connection (HTTP/1 without keep-alive)
Pool behaviours
def acquire
def acquire resource = wait_for_resource return resource unless block_given? begin yield resource ensure release(resource) end end
def availability_string
def availability_string @resources.collect do |resource,usage| "#{usage}/#{resource.multiplex}#{resource.connected? ? '' : '*'}/#{resource.count}" end.join(";") end
def available_resource
def available_resource # This is a linear search... not idea, but simple for now. @resources.each do |resource, count| if count < resource.multiplex # We want to use this resource... but is it connected? if resource.connected? @resources[resource] += 1 return resource else retire(resource) end end end if !@limit or @active < @limit Async.logger.debug(self) {"No resources resources, allocating new one..."} @active += 1 return create end return nil end
def busy?
def busy? @resources.collect do |_,usage| return true if usage > 0 end return false end
def close
def close @resources.each_key(&:close) @resources.clear @active = 0 end
def create
def create # This might return nil, which means creating the resource failed. if resource = @constructor.call @resources[resource] = 1 end return resource end
def empty?
def empty? @resources.empty? end
def initialize(limit = nil, &block)
def initialize(limit = nil, &block) @resources = {} # resource => count @available = Async::Notification.new @limit = limit @active = 0 @constructor = block end
def release(resource)
def release(resource) # A resource that is not good should also not be reusable. if resource.reusable? reuse(resource) else retire(resource) end end
def retire(resource)
def retire(resource) Async.logger.debug(self) {"Retire #{resource}"} @resources.delete(resource) @active -= 1 resource.close @available.signal end
def reuse(resource)
def reuse(resource) Async.logger.debug(self) {"Reuse #{resource}"} @resources[resource] -= 1 @available.signal end
def to_s
def to_s "\#<#{self.class} resources=#{availability_string} limit=#{@limit.inspect}>" end
def wait_for_resource
def wait_for_resource # If we fail to create a resource (below), we will end up waiting for one to become resources. until resource = available_resource @available.wait end Async.logger.debug(self) {"Wait for resource #{resource}"} return resource end