Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I do thread safe singleton with Rails, how keep my classes variables safe?

I have read Is Rails shared-nothing or can separate requests access the same runtime variables? and they explain my problem:

class variable are maybe share between two request to my rails srver, but where is the solution!?

How can I implement a safe singleton between request?

class Foo
  @@instances = []
end

How can I be sure instances will be reset for each request HTTP?!

EDIT:

I find "config.reload_classes_only_on_change = false" solution but i'm not sure it's the best for performance.
What are the consequences to this option?

I have an exemple to test safe classes variables :

class Test
   def self.log
      @test ||= false
      puts @test
      @test = true
   end
end


class ApplicationController < ActionController::Base
   def index
      Test.log
      Test.log
   end
end

if I start this code with reloading action (F5), I want to read "false" each time in log of rails server. But, by default it's "false" only the first time.

EDIT 2: In fact this option reload class, but not resolve the concurency problem in thread. Classes variables are reset but they can be modify by other thread.

How threadsafe classes variables?

like image 968
Matrix Avatar asked Nov 27 '25 05:11

Matrix


1 Answers

I use the request_store gem, it works great.

My use case is for adding methods to the user model class, like the current_user, their locale, location etc as often my other models need this information.

I just setup the current user from my application controller:

User.current = the_authenticated_user
User.request = request

And in my user model class:

class User
  def self.current
    RequestStore.store[:current_user]
  end

  def self.current=(user)
    RequestStore.store[:current_user] = user
  end

  def self.request
    RequestStore.store[:current_request]
  end

  def self.request=(request)
    # stash the request so things like IP address and GEO-IP based location is available to other models
    RequestStore.store[:current_request] = request
  end

  def self.location
    # resolve the location just once per request
    RequestStore.store[:current_location] ||= self.request.try(:location)
  end
end

I don't enable the reload classes option as it causes too many problems, I've witnessed multiple versions of classes hanging around. If you use model inheritance (ie STI) lazy loading and/or dynamic class loading will often break how model classes are resolved. You need to use require_dependency in your base and intermediate model classes to ensure downstream classes also get loaded.

My development settings mirror my production settings wrt class handling which is not convenient (requires server restart after a change) but more convenient than chasing non-existent bugs. The rerun gem can monitor file-system changes and restart your server for you so that you get reliable change handling in development, albeit slower than rails broken class reloading.

config/environment/development.rb:

# Rails class reloading is broken, anytime a class references another you get multiple
# class instances for the same named class and that breaks everything. This is especially
# important in Sequel as models resolve classes once.
# So always cache classes (true)
config.cache_classes = true

# Always eager load so that all model classes are known and STI works
config.eager_load = true

Q: How threadsafe classes variables?

A: No variables are thread safe unless protected by synchronize.

From an architectural perspective threading in Rails is a waste of time. The only way I have been able to get true parallel performance/concurrency is multiple processes. It also avoids locking and threading related overheads that just don't exist with long running processes. I tested parallel CPU intensive code using threads with Ruby 2.x and got no parallelism at all. With 1 ruby process per core I got real parallelism.

I would seriously consider Thin with multiple processes and then decide if you want to use Thin+EventMachine to increase overall throughput per process.

like image 102
Andrew Hacking Avatar answered Nov 29 '25 22:11

Andrew Hacking