Larry’s Blog

Global Variables in Rails

| Comments

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?

Ruby Global Variables

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.

Thread.current

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.

With Thread.current method, you could get the currently executing thread. Then you could use Thread#[] and Thread#[]= to get and set thread-local variables, respectively.

1
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 Thread.thread_variable_set and Thread.thread_variable_get.

But there is one problem.

If you use Thread.current with fancy evented/threaded web servers like Thin or 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:

1
2
3
4
5
6
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 Thin or Puma, you get 1, then 2, then 3

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.

1
2
3
4
5
6
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.

But with Thread.current(or 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 Thread.current[].

And with these two pains, we may ask: What’s the better solution?

ActiveSupport::PerThreadRegistry

ActiveSupport::PerThreadRegistry module is used to encapsulate access to thread local variables.

Instead of polluting the thread locals namespace:

1
Thread.current[:connection_handler]

We could define a class that extends this module:

1
2
3
4
5
6
module ActiveRecord
  class RuntimeRegistry
    extend ActiveSupport::PerThreadRegistry
    attr_accessor :connection_handler
  end
end

and invoke the declared instancec accessors as class methods. So

1
ActiveRecord::RuntimeRegistry.connection_handler = connection_handler

sets a connection handler local to the current thread, and

1
ActiveRecord::RuntimeRegistry.connection_handler

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 Thread.current.

The implementation is pretty simple, too. See the codes here: module PerThreadRegistry.

With 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.

Wrap It Up

  • 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.

  • Use ActiveSupport::PerThreadRegistry for better global variables management and document.

References

Comments