Skip to content

Duplicate the cached value before writing it in the local cache#37587

Merged
rafaelfranca merged 1 commit intorails:masterfrom
Shopify:fix-local-cache-leak
Oct 29, 2019
Merged

Duplicate the cached value before writing it in the local cache#37587
rafaelfranca merged 1 commit intorails:masterfrom
Shopify:fix-local-cache-leak

Conversation

@casperisfine
Copy link
Copy Markdown
Contributor

Discovered by @eljojo.

I think the test case showcase the bug somewhat properly. In short it was already protecting itself from mutation of returned values, but not from mutation of the original value, e.g.:

my_string = "foo"
cache.write('key', my_string)
my_string << "bar"
cache.read('key') # => "foobar"

@rafaelfranca @camilo @etiennebarrie @Edouard-chin

def write_entry(key, value, **options)
@data[key] = value
def write_entry(key, entry, **options)
entry.dup_value!
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this do deep duping?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does a Marshal.load

@value = Marshal.load(Marshal.dump(@value))

@rafaelfranca
Copy link
Copy Markdown
Member

I'm pretty sure we have a PR for this already.

@rafaelfranca
Copy link
Copy Markdown
Member

Ah, it was #36656

@rafaelfranca rafaelfranca merged commit 58fe42e into rails:master Oct 29, 2019
@Edouard-chin
Copy link
Copy Markdown
Member

Yeah #36656 fixed a similar issue but only protecting when returning a value

casperisfine pushed a commit to Shopify/rails that referenced this pull request Apr 19, 2021
The local cache need to protect against mutations of both
the initial received value, and the value returned.

See:

  - rails#36656
  - rails#37587

Because of this, the overhead of the local cache end up being
quite significant. On a single read, the value will be deep duped
twice. So unless the value is one of the type benefiting from a
fast path, we'll have to do two `Marshal.load(Marshal.dump(value))`
roundtrips, which for large values can be very expensive.

By using a specialized `LocalEntry` type instead, we can store
the `Marshal` payload rather than the original value. This way
the overhead is reduced to a single `Marshal.dump` on writes
and a single `Marshal.load` on reads.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants