class Pond

Constants

DEFAULT_DETACH_IF
VERSION

Attributes

allocated[R]
available[R]
collection[R]
detach_if[R]
maximum_size[R]
timeout[R]

Public Class Methods

new( maximum_size: 10, eager: false, timeout: 1, collection: :queue, detach_if: DEFAULT_DETACH_IF, &block ) click to toggle source
# File lib/pond.rb, line 14
def initialize(
  maximum_size: 10,
  eager: false,
  timeout: 1,
  collection: :queue,
  detach_if: DEFAULT_DETACH_IF,
  &block
)
  @block   = block
  @monitor = Monitor.new
  @cv      = MonitorMixin::ConditionVariable.new(@monitor)

  @allocated = {}
  @available = Array.new(eager ? maximum_size : 0, &block)

  self.timeout      = timeout
  self.collection   = collection
  self.detach_if    = detach_if
  self.maximum_size = maximum_size
end

Private Class Methods

wrap(*args, &block) click to toggle source
# File lib/pond.rb, line 151
def wrap(*args, &block)
  Wrapper.new(*args, &block)
end

Public Instance Methods

checkout(scope: nil) { |object| ... } click to toggle source
# File lib/pond.rb, line 35
def checkout(scope: nil, &block)
  raise "Can't checkout with a non-frozen scope" unless scope.frozen?

  if object = current_object(scope: scope)
    yield object
  else
    checkout_object(scope: scope, &block)
  end
end
collection=(type) click to toggle source
# File lib/pond.rb, line 54
def collection=(type)
  raise "Bad value for Pond collection: #{type.inspect}" unless [:stack, :queue].include?(type)
  sync { @collection = type }
end
detach_if=(callable) click to toggle source
# File lib/pond.rb, line 67
def detach_if=(callable)
  raise "Object given for Pond detach_if must respond to #call" unless callable.respond_to?(:call)
  sync { @detach_if = callable }
end
maximum_size=(max) click to toggle source
# File lib/pond.rb, line 59
def maximum_size=(max)
  raise "Bad value for Pond maximum_size: #{max.inspect}" unless Integer === max && max >= 0
  sync do
    @maximum_size = max
    {} until size <= max || pop_object.nil?
  end
end
size() click to toggle source
# File lib/pond.rb, line 45
def size
  sync { @allocated.inject(@available.size){|sum, (h, k)| sum + k.length} }
end
timeout=(timeout) click to toggle source
# File lib/pond.rb, line 49
def timeout=(timeout)
  raise "Bad value for Pond timeout: #{timeout.inspect}" unless Numeric === timeout && timeout >= 0
  sync { @timeout = timeout }
end

Private Instance Methods

checkout_object(scope:) { |current_object(scope: scope)| ... } click to toggle source
# File lib/pond.rb, line 74
def checkout_object(scope:)
  lock_object(scope: scope)
  yield current_object(scope: scope)
ensure
  unlock_object(scope: scope)
end
current_object(scope:) click to toggle source
# File lib/pond.rb, line 138
def current_object(scope:)
  sync { (@allocated[scope] ||= {})[Thread.current] }
end
get_object(timeout) click to toggle source
# File lib/pond.rb, line 127
def get_object(timeout)
  pop_object || size < maximum_size && @block || @cv.wait(timeout) && false
end
lock_object(scope:) click to toggle source
# File lib/pond.rb, line 81
def lock_object(scope:)
  deadline = Time.now + @timeout

  until current_object(scope: scope)
    raise Timeout if (time_left = deadline - Time.now) < 0

    sync do
      if object = get_object(time_left)
        set_current_object(object, scope: scope)
      end
    end
  end

  # We need to protect changes to @allocated and @available with the monitor
  # so that #size always returns the correct value. But, we don't want to
  # call the instantiation block while we have the lock, since it may take a
  # long time to return. So, we set the checked-out object to the block as a
  # signal that it needs to be called.
  if current_object(scope: scope) == @block
    set_current_object(@block.call, scope: scope)
  end
end
pop_object() click to toggle source
# File lib/pond.rb, line 131
def pop_object
  case collection
  when :queue then @available.shift
  when :stack then @available.pop
  end
end
set_current_object(object, scope:) click to toggle source
# File lib/pond.rb, line 142
def set_current_object(object, scope:)
  sync { (@allocated[scope] ||= {})[Thread.current] = object }
end
sync(&block) click to toggle source
# File lib/pond.rb, line 146
def sync(&block)
  @monitor.synchronize(&block)
end
unlock_object(scope:) click to toggle source
# File lib/pond.rb, line 104
def unlock_object(scope:)
  object               = nil
  detach_if            = nil
  should_return_object = nil

  sync do
    object               = current_object(scope: scope)
    detach_if            = self.detach_if
    should_return_object = object && object != @block && size <= maximum_size
  end

  begin
    should_return_object = !detach_if.call(object) if should_return_object
    detach_check_finished = true
  ensure
    sync do
      @available << object if detach_check_finished && should_return_object
      @allocated[scope].delete(Thread.current)
      @cv.signal
    end
  end
end