When you are using Unicorn or Passenger Phusion based (community edition), you don’t have to worry about thread safety, Because these servers are multi-process (workers) based servers.
Read about application server here
But when comes to Puma, It comes with Multi-threaded options as well. From MRI background, we tend not to write thread-safety code. This will cause issues in multi-threaded environment like Puma. If you are going to migrate application server from Unicorn/Passenger to Puma (with multi-threaded code), make sure you follow few tips.
Ruby doesn’t gives you immutable data structure, Even constant is mutable in Ruby’s world.
2.5.0 :001 > IAM_CONSTANT = "test"
=> "test"
2.5.0 :002 > IAM_CONSTANT = "new_test"
(irb):2: warning: already initialized constant IAM_CONSTANT
(irb):1: warning: previous definition of IAM_CONSTANT was here
=> "new_test"
2.5.0 :003 > IAM_CONSTANT
=> "new_test"
2.5.0 :001 > IAM_ARRAY_CONSTANT = ["Haha! I am constant, no one can change me!"]
=> ["Haha! I am constant, no one can change me!"]
2.5.0 :002 > IAM_ARRAY_CONSTANT << "Seriously?"
=> ["Haha! I am constant, no one can change me!", "Seriously?"]
2.5.0 :003 >
2.5.0 :001 > IAM_ARRAY_CONSTANT = ["Haha! I am constant, no one can change me!"].freeze
=> ["Haha! I am constant, no one can change me!"]
2.5.0 :002 > IAM_ARRAY_CONSTANT << "Seriously?"
Traceback (most recent call last):
2: from /Users/satyanarayan/.rvm/rubies/ruby-2.5.0/bin/irb:11:in `<main>'
1: from (irb):2
FrozenError (can't modify frozen Array)
I have worked with Legacy code-bases before where we used to determine current user using Global variable. It worked fine when we have used Application servers like Passenger. Those code will not work with Puma like servers.
class UsersController < ApplicationController
before_action :authenticate_user
def authenticate_user
$user_id = fetch_user_from_cookies
sleep(10)
if $user_id == "admin"
Rails.logger.info("Show admin template")
else
Rails.logger.info("Show normal template")
end
end
def index
render json: ["Hey i am working fine"]
end
end
Started GET "/test?user_id=admin" for ::1 at 2020-02-16 15:48:46 +0530
Processing by TestController#index as HTML
Parameters: {"user_id"=>"admin"}
Started GET "/test?user_id=admi" for ::1 at 2020-02-16 15:48:48 +0530
Processing by TestController#index as HTML
Parameters: {"user_id"=>"admi"}
Show normal template
Completed 200 OK in 10ms (Views: 0.2ms | ActiveRecord: 0.0ms)
Show normal template
Completed 200 OK in 10ms (Views: 1.2ms | ActiveRecord: 0.0ms)
Even class method is not thread safe, If you mutating class variable, that will make your code smell
Memoization is for sure good practice, when you want to cache across multiple calls inside object. Make sure whichever memoizing inside class method can shared across multiple request as well.
class UsersController < ApplicationController
before_action :fetch_user
def fetch_user
@user = User.find_user_once(@user_id)
end
def index
render json: ["Hey it works"]
end
end
class User
def self.find_user_once(user_id)
@test ||= User.find(user_id)
end
end
Read documentation/code to confirm if library that you are going to use is thread-safe