memoist
memoist copied to clipboard
Global cache flush
Is there a way to globally flush the cache?
I have some nasty order dependent bugs in my rspecs where objects with their caches seem to be leaking from one spec to another.
So far, the best workaround I came up with is this
ObjectSpace.each_object { |o| o.flush_cache if o.respond_to? :flush_cache }
horrible workaround in my spec/spec_helper.rb.
Can we do better?
+1
I did this for now, seems faster but doesn't support instances (which is OK for specs, I guess):
require 'binding_of_caller'
require 'memoist'
module Memoist
@@_classes = Set.new
alias old_memoize memoize
def memoize(*method_names)
@@_classes << if self.singleton_class?
binding.callers[2].receiver
else
self
end
old_memoize(*method_names)
end
def self.flush_cache
@@_classes.each { |x| x.try(:flush_cache) }
end
end
# spec_helper
config.after(:each) do
Memoist.flush_cache
end
Can you do
ObjectSpace.each_object(Memoist) { |o| o.flush_cache }
I believe each class that extends memoist should be handled by that... It should be faster
This is interesting.
You don't want to retain a list of all memoised objects, or else they can't be garbage collected.
But you could selectively add objects to a list And flush the cache.
Something like
memoize :foo, track: true
But Id say, if you`re memoizing a whole bunch of global objects, You're probably doing something wrong.
If you handcoded your class
class Foo
def self.bar
@bar ||= something_you_want_to_memoizr
end
end
How would you flush that?
Bumping a 5 year old issue! 💯
We're not having issues with memoized instances leaking between specs, but we've hit a nasty bit of dependency using class-level memoization.
Here's our solution (currently sits in our spec code but can be extracted):
memoized_classes = ObjectSpace.each_object(Class).select do |obj|
obj.included_modules.include? Memoist::InstanceMethods
end
memoized_classes.each do |mobj|
mobj.all_memoized_structs.each do |struct|
mobj.remove_instance_variable(struct.ivar) if mobj.instance_variable_defined?(struct.ivar)
end
end
Class memoization occurs at the singleton class level, but flush_cache
only exists at class level. I haven't found a reliable way to grab the class (as opposed to singleton class) from ObjectSpace. As a result, we need to duplicate some of the logic from within Memoist to clear out the ivars.
This approach doesn't introduce the GC issues of tracking every memoized object, but constantly scanning ObjectSpace for classes isn't the most efficient approach either... you need to check after each spec if you're you're not preloading the entire application though.
@JoeMcB what's a minimal example of the problem?
eg. the tests memoize the Book
class, and flush_cache
works fine?
https://github.com/matthewrudy/memoist/blob/510cb571cdea0fcb72fa8ec7560bc876f933f442/test/memoist_test.rb#L308-L316
We're not having issues with memoized instances leaking between specs, but we've hit a nasty bit of dependency using class-level memoization.
Here's our solution (currently sits in our spec code but can be extracted):
@JoeMcB in addition to the minimal example, can you describe the problem? I'm not sure what you mean by nasty bit of dependency using class-level memoization
What is your solution solving? Test contamination?
Yep, that works fine! I actually submitted the PR for that feature. :)
Our issue isn't calling flush on any memoized Class, it's needing to call flush on every memoized class. In our specific case, we're memoizing quite a few objects (at a class level) through our application. We've been adding a line ClassName.flush_cache
to a before hook for our test suite in order to make sure memoized data isn't persisting between tests.
config.before(:each) do
Design::OptionAttributeCache.flush_cache
Product::SubclassPriceService.flush_cache
# and so on
end
We've seen more than a few instances of a developer forgetting to add their newly memoized class into that spec configuration though. It'd be considerably easier from a testing perspective to have a Memoist.flush_all
method that flushes every memoized class at once to future proof our specs against additional uses of Memoist.
The code posted above is working for us. I can wrap that in a PR if it'd be helpful for the base library.
@JoeMcB be careful though, perhaps the code isn't clearing the cache when it should be. Maybe the tests are trying to demonstrate this?
We have one mixin we use memoist for with many methods in ManageIQ, in our relationship mixin, which is included in many of our classes. We call flush_cache
in one place, the clear_relationship_cache
here.
If you notice, we call clear_relationship_cache
whenever we're making changes to a relationship so it's always unset and clean so the new relationship can be memoized.
Sorry, that was a super complicated example but the point is to have one entrypoint to the memoization so you can easily clear the caching when you need to store a new value.