lib/concurrent/dereferenceable.rb



module Concurrent

  # Object references in Ruby are mutable. This can lead to serious problems when
  # the +#value+ of a concurrent object is a mutable reference. Which is always the
  # case unless the value is a +Fixnum+, +Symbol+, or similar "primitive" data type.
  # Most classes in this library that expose a +#value+ getter method do so using
  # this mixin module.
  module Dereferenceable

    # Return the value this object represents after applying the options specified
    # by the +#set_deref_options+ method.
    #
    # When multiple deref options are set the order of operations is strictly defined.
    # The order of deref operations is:
    # * +:copy_on_deref+
    # * +:dup_on_deref+
    # * +:freeze_on_deref+
    #
    # Because of this ordering there is no need to +#freeze+ an object created by a
    # provided +:copy_on_deref+ block. Simply set +:freeze_on_deref+ to +true+.
    # Setting both +:dup_on_deref+ to +true+ and +:freeze_on_deref+ to +true is
    # as close to the behavior of a "pure" functional language (like Erlang, Clojure,
    # or Haskell) as we are likely to get in Ruby.
    # 
    # This method is thread-safe and synchronized with the internal +#mutex+.
    #
    # @return [Object] the current value of the object
    def value
      mutex.synchronize do
        apply_deref_options(@value)
      end
    end
    alias_method :deref, :value

    protected

    # A mutex lock used for synchronizing thread-safe operations. Methods defined
    # by +Dereferenceable+ are synchronized using the +Mutex+ returned from this
    # method. Operations performed by the including class that operate on the
    # +@value+ instance variable should be locked with this +Mutex+.
    #
    # @return [Mutex] the synchronization object
    #
    # @!visibility public
    def mutex
      @mutex
    end

    # Initializes the internal +Mutex+. 
    #
    # @note This method *must* be called from within the constructor of the including class.
    #
    # @see #mutex
    #
    # @!visibility public
    def init_mutex
      @mutex = Mutex.new
    end

    # Set the options which define the operations #value performs before
    # returning data to the caller (dereferencing).
    #
    # @note Most classes that include this module will call +#set_deref_options+
    # from within the constructor, thus allowing these options to be set at
    # object creation.
    #
    # @param [Hash] opts the options defining dereference behavior.
    # @option opts [String] :dup_on_deref (false) call +#dup+ before returning the data
    # @option opts [String] :freeze_on_deref (false) call +#freeze+ before returning the data
    # @option opts [String] :copy_on_deref (nil) call the given +Proc+ passing the internal value and
    #   returning the value returned from the proc
    #
    # @!visibility public
    def set_deref_options(opts = {})
      mutex.synchronize do
        @dup_on_deref = opts[:dup_on_deref] || opts[:dup]
        @freeze_on_deref = opts[:freeze_on_deref] || opts[:freeze]
        @copy_on_deref = opts[:copy_on_deref] || opts[:copy]
        @do_nothing_on_deref = ! (@dup_on_deref || @freeze_on_deref || @copy_on_deref)
      end
    end

    # @!visibility private
    def apply_deref_options(value) # :nodoc:
      return nil if value.nil?
      return value if @do_nothing_on_deref
      value = value
      value = @copy_on_deref.call(value) if @copy_on_deref
      value = value.dup if @dup_on_deref
      value = value.freeze if @freeze_on_deref
      value
    end
  end
end