lib/future-resource.rb in future-resource-1.0.0 vs lib/future-resource.rb in future-resource-1.1.0

- old
+ new

@@ -21,63 +21,110 @@ # class FutureResource ## # Create a new FutureResource. # - def initialize + def initialize(blocker = nil) @resource_lock = Monitor.new - @resource_value_blocker = @resource_lock.new_cond + @resource_value_blocker = blocker || @resource_lock.new_cond + @terminated = false end - + ## # Checks if the value of the resource placeholder has been set yet. # # @return [Boolean] # def set_yet? !!@resource_lock.synchronize { defined? @resource } end ## - # Returns the value of a specific resource, optionally waiting for `timeout` seconds before raising a Timeout::Error exception. - # When called on a not set resource without a timeout, raises a deadlock. + # Checks if the attempt to read the resource has been terminated. # + # @return [Boolean] + # + def terminated? + !!@resource_lock.synchronize { @terminated } + end + + ## + # Returns the value of a specific resource, optionally waiting for `timeout` seconds before + # raising a Timeout::Error exception, or raising a FutureResource::Terminated exception if + # the attempt to read the resource is terminated early by another thread. When called on + # an unset resource without a timeout, raises a deadlock. + # # @param [Integer] timeout number of seconds to wait for the resource to become ready # # @raise [Timeout::Error] if timeout expires and resource is not ready + # @raise [FutureResource::Terminated] if the attempt to read the resource is terminated by another thread # # @return [Object] # def resource(timeout = nil) Timeout::timeout timeout do @resource_lock.synchronize do - @resource_value_blocker.wait unless defined? @resource + @resource_value_blocker.wait unless set_yet? or terminated? + raise Terminated if terminated? @resource end end end - + ## # Sets the value for the resource, making it available for all waiting and following reads. - # Resourcs values can only be set once. + # Resource values can only be set once. Calling this method on a terminated resource is + # ineffective. # # @param [Object] resource any value to be set for the resource # # @raise [FutureResource::ResourceAlreadySet] if resource is already set + # def resource=(resource) - @resource_lock.synchronize do - raise ResourceAlreadySetException if defined? @resource + set_or_terminate do @resource = resource - @resource_value_blocker.broadcast - @resource_value_blocker = nil # Don't really need it anymore. end end ## + # Terminates the attempt to read the resource early, causing those waiting and any + # following reads to raise a FutureResource::Terminated exception. Subsequently calling + # this method again is ineffective. + # + # @raise [FutureResource::ResourceAlreadySet] if resource is already set + # + def terminate + set_or_terminate do + @terminated = true + end + end + + ## # Raised when the program tries to set a value for the resource that is already set. # class ResourceAlreadySetException < StandardError def initialize super "Cannot set this resource twice!" + end + end + + ## + # Raised when the attempt to read the resource is terminated early. + # + class Terminated < StandardError + def initialize + super "Resource read attempt terminated" + end + end + + private + + def set_or_terminate + @resource_lock.synchronize do + return if terminated? + raise ResourceAlreadySetException if set_yet? + yield + @resource_value_blocker.broadcast + @resource_value_blocker = nil # Don't really need it anymore. end end end