Thread-safety on Rails

Since many of us Rubyists work on Rails applications on a regular basis, you should know how to keep your Rails apps thread-safe.

It’s rare that you would spawn threads inside your application logic, but if you’re using a multi-threaded web server (like Puma%{}) or a multi-threaded background job processor (like Sidekiq%{}), then your application is running in a multi-threaded context.

The last chapter said that good, idiomatic Ruby code is usually thread-safe Ruby code. The same goes for Rails applications. If you stick to Rails conventions, and write idiomatic Rails code, your Rails application will be thread-safe.

Gem dependencies

Outside of your application code, you’ll have to make sure that any gems you depend on are thread-safe. Since there’s no way to programmatically verify thread safety, you’ll have to do your research. The majority of gems in the community are thread-safe, but check the bug tracker of any gem you’re thinking of adding, just to be sure.

The request is the boundary

The most important bit of information you need in order to understand thread safety in web applications is how threads are being used by the underlying web server or background job processor.

For the web server, the web request is the dividing line between threads. By this I mean that a multi-threaded web server will process each request with a separate thread.

If you know that each request is handled by a separate thread, then the path to thread safety is clear: don’t share objects between requests. This will ensure that no objects are shared between threads.

To put it even more simply, make sure that each controller action creates the objects that it needs to work with, rather than sharing them between requests via a global reference.

A good example of this is something like a User.current reference. All Rails applications have some way of referring to the current user in a controller action. Typically this is done using an instance variable. This is safe because each request creates its own instance of the controller stack; variables aren’t shared. However, if you store a User object in User.current, that’s a class variable that is shared among threads.

So one thread will assign a user to User.current, then another thread can come along and overwrite it. The first thread expects to find its user reference still there when performing database lookups, but it’s been overwritte. Now your users end up seeing each others data instead of their own.

If you really need a global reference, follow the guidelines from the last chapter. Try using a thread-local, or else a thread-aware object that will preserve data correctness.

The same heuristic is applicable to a background job processor. Each job will be handled by a separate thread. A thread may process multiple jobs in its lifetime, but a job will only be processed by a single thread in its lifecycle.

Again, the path to thread safety is clear: create the necessary objects that you need in the body of the job, rather than sharing any global state.

Following these guidelines will ensure that your Rails application code has no safety issues in a multi-threaded context.