Is it thread safe to use memoization on class variables?

class Blog
  
  def self.articles
    @@articles ||= Dir.glob(Rails.root.join('app', 'views', 'articles', '*.html.erb')).map do |file|
      parse_file(file).front_matter
    end
  end

end

Is the above code thread safe / safe?

(i.e. I am asking about the use of @@articles ||= to cache the expensive operation)

Since ||= is in fact two operations, your code would not prevent multiple executions of Dir.glob... at the same time. In that sense it is not “thread safe”.

Whether this creates a real problem for your application depends on your context (performance constraints, side effects of Dir.glob..., etc.)

Sidekiq wiki and How Do I Know Whether My Rails App Is Thread-safe or Not? both recommend not using class variables, but these are general guidelines and your concrete situation might be different.

1 Like

For my understanding the worst case scenario is that two threads (or more) start reading the files together and then they both assign (the same computed value) to the variable.

I guess that the assignment operation (=) is atomic, so it should not create a corrupted state for the class variable in my case (the files are static and are only read).

That’s right. However, for a slightly more elegant approach, you could load these cached values on initialization stage. For example, if you add a config/initializers/load_articles.rb:

Rails.application.config.to_prepare do
  Blog.articles = Dir.glob … # Assuming you have Blog.articles=.
end

This will not only load them once in a thread-safe manner, but would also (if memory serves right) reload these files in development, if you update/add/remove articles, without having to restart the Rails server.

4 Likes