Should class instance variables in Rails be set within a mutex?

ruby, ruby-on-rails, ruby-on-rails-3, synchronization

Solution

It's possible (and desirable) to run Rails in a multi-threaded mode even in MRI. This can be accomplished by changing a line in `production.rb`.

config.threadsafe!

In MRI, two threads cannot run code simultaneously, but a context switch can happen at any time. In Rubinius and JRuby, threads can run code simultaneously.

Let's look at the code you showed:

class Something
  def self.objects
    @objects ||= begin
      # some logic that builds an array, which is ultimately stored in @objects
    end
  end
end

The `||=` code gets expanded to something like:

class Something
  def self.objects
    @objects || (@objects = begin
      # some logic that builds an array, which is ultimately stored in @objects
    end)
  end
end

This means that there are actually two steps to the process:

- look up `@objects`

- If `@objects` is falsy, set `@objects` to the results of the `begin/end` expression

It may be possible for the context to switch between these steps. It is certainly possible for the context to switch in the middle of step 2. This means that you may end up running the block multiple times instead of once. In MRI, this may be acceptable, but it's perfectly straight forward to lock a mutex around the expression, so do it.

class Something
  MUTEX = Mutex.new

  def self.objects
    MUTEX.synchronize do
      @objects ||= begin
        # some logic that builds an array, which is ultimately stored in @objects
      end
    end
  end
end

Problem

Let's say I've got a Ruby class in my Rails project that is setting an instance variable. ``` class Something def self.objects @objects ||= begin # some logic that builds an array, which is ultimately stored in @objects end end end ``` Is it possible that `@objects` could be set multiple times? Is it possible that during one request, while executing code between the `begin`/`end` above, that this method could be called during a second request? This really comes down to a question of how Rails server instances are forked, I suppose. Should I instead be using a `Mutex` or thread synchronization? e.g.: ``` class Something def self.objects return @objects if @objects Thread.exclusive do @objects ||= begin # some logic that builds an array, which is ultimately stored in @objects end end end end ```

Original source