Thread-Safety in Ruby on Rails: Basic Example With Race Conditions
The Global Interpreter Lock GIL in Ruby prevents true multi core CPU usage in a Rails app, but there is still concurrency, especially when using a multi threaded server like Pum...
Rubycode
22 October 2024 · 2 min read
The Global Interpreter Lock (GIL) in Ruby prevents true multi-core CPU usage in a Rails app, but there is still concurrency, especially when using a multi-threaded server like Puma. While Ruby cannot execute multiple threads in parallel due to the GIL, Puma can handle multiple requests concurrently. This means that when one thread is waiting for a database transaction or an external service, other threads can continue working.
Imagine we have a simple controller that increments the page view counter. If 100 users hit the same page within one second, we could face a problem. Without using atomic operations or a Mutex, some of those page views could be lost, and the counter might not reflect the correct total. This happens due to a race condition, where multiple threads are trying to update the same resource at the same time.
BAD PRACTICE
class PageViewsController < ApplicationController
def show
@page_view = PageView.find(params[:id])
@page_view.view_count += 1
@page_view.save
render json: @page_view
end
end
If multiple users request this page at the same time, say 5 users hit the show action concurrently, the following could happen:
Each user (request) fetches the same PageView record from the database.
Each request reads the current view_count (e.g., 100).
Each request increments the view_count to 101.
Each request saves view_count as 101 in the database.
Even though 5 requests were made, the view_count was only incremented by 1 instead of 5 because the changes made by one request are overwritten by others. This is a classic race condition due to concurrent database access in a multi-threaded environment (handled by Puma)
GOOD PRACTICE
class PageViewsController < ApplicationController
def show
@page_view = PageView.find(params[:id])
@page_view.increment!(:view_count)
render json: @page_view
end
end
The increment! method generates an SQL query that increments the view_count directly in the database in an atomic way. Even if multiple requests hit the server at the same time, the database ensures that each increment operation is applied correctly, preventing lost updates
More from the blog
All articles-
Engineering
How to write an implementation plan your team can trust
What every implementation plan for a Rails feature must have, from the decisions only people can make to safe migrations, testable acceptance criteria and a sign-off, and how to make your team follow it every time with questions, settings and guards in code.
06 October 2026 · 11 min read
-
AI Engineering
Where Is the Engineering Work Now? Redesigning Development Around AI
Agents already write most of the code in a sprint, yet we still run it as if every developer types their own ticket. Where the real engineering work is today, and what changes when you redesign the process around AI.
02 October 2026 · 5 min read
-
Gems
The agents write the code. Your team decides what gets built.
plan_driven is an open source Ruby gem that takes a Rails feature from an approved implementation plan to merged, tested pull requests, with one Cursor agent per ticket and guards written in Ruby at every step.
30 September 2026 · 17 min read