In a Rails application, sometimes you may wanna use global variables for every request, with which you don't have to send the object as a parameter everywhere, like
current_user for model layers (which may not be a good idea).
So how could we do that?
If you know Ruby well, you may know variable with a beginning with
$ is global.
But global variables with
$ prefix are supposed to be accessible from every single palce of your code, so they are shared among all threads, and that's definitely not what we want, right?
So basically, don't use Ruby native global variables, ever.
So we want our global variables to be thread-safe, a.k.a thread-local.
This is where
Thread comes in.
Threads are the Ruby implementation for a concurrent programming model.
Programs that require multiple threads of execution are a perfect candidate for Ruby's Thread class.
Thread.current[:current_user] = user
If you read the links of ruby-doc carefully, you may notice that actually the variable is fiber-local instead of thread-local. Since we rarely use fibers these days, especially for new 2.x Ruby versions, we could assume this is equal to thread-local. But if you do use fibers, and want your variables to be thread-local, please use
But there is one problem.
If you use
Thread.current with fancy evented/threaded web servers like
Puma, please watch out! Values can stick around longer that you'd expect, and this can cause bugs. For example, if we had this in our controller:
def index Thread.current[:counter] ||= 0 Thread.current[:counter] += 1 render :text => Thread.current[:counter] end
If we ran this on MRI with Webrick, you'd get
1 as output, every time. But if you run it with
Puma, you get
So what's the solution?
Steve Klabnik releases a gem called request_store to do that for you. Everywhere you used
Thread.current, just change it to
RequestStore.store. And no matter what server you use, you'll get
1 every time: the storage is local to that request.
def index RequestStore.store[:counter] ||= 0 RequestStore.store[:counter] += 1 render :text => RequestStore.store[:counter] end
Codes of the gem are pretty simple, just insert a middleware to Rails and use
Thread.current[:request_store] to store variables, and clear
Thread.current[:request_store] after every request.
RequestStore.store), there are two pains may bother you later:
1. Someone could accidentally overwrite your data.
If the other developer picks the same key with yours, and overwrite it somewhere, you app will just break. Or if you are a gem author, that is really something you need to consider.
2. It's not well-structured.
Or we could say that it's not very OO. You don't know what's in your
Thread.current, and you're gonna have to read every line of codes with
And with these two pains, we may ask: What's the better solution?
ActiveSupport::PerThreadRegistry module is used to encapsulate access to thread local variables.
Instead of polluting the thread locals namespace:
We could define a class that extends this module:
module ActiveRecord class RuntimeRegistry extend ActiveSupport::PerThreadRegistry attr_accessor :connection_handler end end
and invoke the declared instancec accessors as class methods. So
ActiveRecord::RuntimeRegistry.connection_handler = connection_handler
sets a connection handler local to the current thread, and
returns a connection handler local to the current thread.
This feature is accomplished by instantiating the class and storing the instance as a thread local keyed by the class name. In the example above a key "ActiveRecord::RuntimeRegistry" is stored in
The implementation is pretty simple, too. See the codes here: module PerThreadRegistry.
PerThreadRegistry module, the previous pains concerning you are gone.
As to the previous
RequestStore problem, someone provides a solution, or you could just hack it yourself. It's quite simple.
Don't ever use Ruby native global variables.
Thread.current could do it, but it's not good for complicated apps or codes of a gem.
Clear thread local variables after every request, or it may stick around longer than you'd expect.
ActiveSupport::PerThreadRegistry for better global variables management and document.