Skip to content

Objects created with dup share cache entry with original object #403

@dferretti

Description

@dferretti

If I create an object that has a memowised value, then .dup to clone that object, the resulting duped object seems to share the same cache entry as the original object. Then these two objects have some sort of invisible coupling.

Example:

require "memo_wise"

class MyTest
  prepend MemoWise

  def initialize(value)
    @value = value
  end

  attr_reader :value

  def computed_value = 2 * @value
  memo_wise :computed_value

  def increment
    @value += 1
    reset_memo_wise
  end
end

original = MyTest.new(5)
puts original.value          # Outputs 5
puts original.computed_value # Outputs 10

duped = original.dup
puts duped.value          # Outputs 5
puts duped.computed_value # Outputs 10

# should just reset duped's memowise cache, but resets both
duped.increment

# calling computed_value on duped will update the cached computed_value for both objects
puts duped.value          # Outputs 6
puts duped.computed_value # Outputs 12

puts original.value          # Outputs 5
puts original.computed_value # X - Outputs 12 instead of 10
Similar example, in reverse order

require "memo_wise"

class MyTest
  prepend MemoWise

  def initialize(value)
    @value = value
  end

  attr_reader :value

  def computed_value = 2 * @value
  memo_wise :computed_value

  def increment
    @value += 1
    reset_memo_wise
  end
end

original = MyTest.new(5)
puts original.value          # Outputs 5
puts original.computed_value # Outputs 10

duped = original.dup
puts duped.value          # Outputs 5
puts duped.computed_value # Outputs 10

# should just reset duped's memowise cache, but resets both
duped.increment

# calling computed_value on original will update the cached computed_value for both objects
puts original.value          # Outputs 5
puts original.computed_value # Outputs 10

puts duped.value          # Outputs 6
puts duped.computed_value # X - Outputs 10 instead of 12

Example with mutable cached value

Here, duped keeps the same exact object instance cached for computed_value, so modifications done to either will be visible in both.
I would have expected that duped's memowise cache would start empty, and allow it to build its own computed_value.

require "memo_wise"

class MyTest
  prepend MemoWise

  def initialize(value)
    @value = value
  end

  attr_reader :value

  def computed_value = { "value" => @value }
  memo_wise :computed_value
end

original = MyTest.new(5)
puts original.value          # Outputs 5
puts original.computed_value # Outputs {"value"=>5}

duped = original.dup
puts duped.value          # Outputs 5
puts duped.computed_value # Outputs {"value"=>5}

duped.computed_value["value"] += 1

puts duped.value          # Outputs 5
puts duped.computed_value # Outputs {"value"=>6}

puts original.value          # Outputs 5
puts original.computed_value # X - Outputs {"value"=>6} instead of {"value"=>5}

puts duped.computed_value.object_id == original.computed_value.object_id # Outputs true which is IMO unexpected

Tested with 1.13.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions