3

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?

Community
  • 1
  • 1
Matrix
  • 3,458
  • 6
  • 40
  • 76

1 Answers1

6

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.

Andrew Hacking
  • 6,296
  • 31
  • 37
  • I found solution with Thread.current like: http://pastebin.com/QzcpQe2h (if we have the uniq instance of main controller, I can add instance variable in it to store my uniq data), but gem are more elegant^^ thx. For options to reload class, can you be explicit: "don't enable" <=> false ? ==> config.reload_classes_only_on_change = false ? And what about : config.cache_classes = ??? & config.eager_load = ??? – Matrix Jan 04 '15 at 00:59
  • Updated answer. Be aware that Thread.current will not clear the thread local storage on request completion so you may have state, resources memory, and other things hanging around if you're not careful, hence recommending that ultra tiny but useful gem. – Andrew Hacking Jan 05 '15 at 09:56
  • I don't understand : It's thread safe or it's not? If we clean all variables added by myself in Thread.current at begin of each request (like gem do), it's good no? – Matrix Jan 05 '15 at 10:01
  • Setting the TLS at request start is fine, and it will be thread safe. But when the request completes, the thread that processed the request will still be hanging onto resources associated with the request. Sure it will get reset at some future point when the thread is used to process a new request but it is nice to have proper closure of a request rather than holding onto resources. It also means your requests start out in a blank state and there is no chance that the previous state/user etc can be used accidentally. – Andrew Hacking Jan 05 '15 at 10:10
  • sry but "TLS"? I understand the idea : to blank all ressources between requests, but what you suggest to do more than use the request_store gem? You don't ask me about config (cf my first comment). thx – Matrix Jan 05 '15 at 10:34
  • TLS -Thread Local Storage. I updated my answer a few hours ago to include config and notified you of that. [See my comment, 2nd row from top]. You can also review the change summary and line by line version differences via the link at the bottom of the answer which says "edited xxx hours ago". I recommend using the gem so cleanup happens at request completion and not using the pastebin you referenced. – Andrew Hacking Jan 05 '15 at 12:27
  • ok, sry I miss your edit post. last thing for me : what's the differents between `config.cache_classes` and `config.reload_classes_only_on_change` configuration? – Matrix Jan 05 '15 at 13:17
  • Its ignored when config.cache_classes is true. See http://guides.rubyonrails.org/configuring.html#rails-general-configuration – Andrew Hacking Jan 05 '15 at 15:45