Mutex for ActiveRecord Model
I found a gem Remote lock when searching for a solution for my problem. It is a mutex solution that uses Redis in the backend.
It:
- is accessible for all processes
- does not lock the database
- is in memory -> fast and no IO
The method looks like this now
def nasty
$lock = RemoteLock.new(RemoteLock::Adapters::Redis.new(REDIS))
$lock.synchronize("capi_lock_#{user_id}") do
http_request_1
http_request_2
update_user
end
end
Mutex for Rails Processes
If you just need to prevent multiple writers from working with a file simultaneously, you can use the File#flock
method to request an exclusive write lock from each process:
fh = File.new("/some/file/path")
begin
fh.flock(File::LOCK_EX)
# ... write to the file here, or perform some other critical operation
ensure
fh.flock(File::LOCK_UN)
end
Note: putting the unlock call in an ensure
block is important to prevent deadlock if an uncaught exception is thrown after you've locked the file.
Mutex in rails API among several client request
A standard ActiveRecord transaction will prevent deadlock, or other contention. You might have to use a pessimistic locking strategy. Databases are designed to prevent exactly this kind of thing.
Rails / ActiveRecord: avoid two threads updating model at the same time with locks
Thanks Robert for the Optimistic Locking info, I could definitely see me going that route, but Optimistic locking works by raising an exception on the moment of writing to the database (SQL UPDATE), and I have a lot of complex business logic that I wouldn't even want to run with the stale data in the first place.
This is how I solved it, and it was simpler than what I imagined.
First of all, I learned that pessimistic locking DOES NOT preventing any other threads from reading that database row.
But I also learned that with_lock
also initiates the lock immediately, regardless of you trying to make a write or not.
So if you start 2 rails consoles (simulating two different threads), you can test that:
If you type
ModelName.last.with_lock { sleep 30 }
on Console 1 andModelName.last
on Console 2, Console 2 can read that record immediately.However, if you type
ModelName.last.with_lock { sleep 30 }
on Console 1 andModelName.last.with_lock { p 'I'm waiting' }
on Console 2, Console 2 will wait for the lock hold by console 1, even though it's not issuing any write whatsoever.
So that's a way of 'locking the read': if you have a piece of code that you want to be sure that it won't be run simultaneously (not even for reads!), begin that method opening a with_lock
block and issue your model reads inside it that they'll wait for any other locks to be released first. If you issue your reads outside it, your reads will be performed even tough some other piece of code in another thread has a lock on that table row.
Some other nice things I learned:
As per rails documentation,
with_lock
will not only start a transaction with a lock, but it will also reload your model for you, so you can be sure that inside the blockModelName.last
is on it's most up-to-date state, since it issues a.reload
on that instance.That are some gems designed specifically to block the same piece of code running at the same time in multiple threads (which I believe the majority of every Rails app is while in production environment), regardless of the database lock. Take a look at redis-mutex, redis-semaphore and redis-lock.
That are many articles on the web (I could find at least 3) that state that Rails
with_lock
will prevent a READ on the database row, while we can easily see with the tests above that's not the case. Take care and always confirm information testing it yourself! I tried to comment on them warning about this.
Does this code require a Mutex to access the @clients variable thread safely?
Any data that may be modified by one thread and read by a different one must be protected by a Mutex or a similar synchronization construct. Inasmuch as multiple threads may safely read the same data at the same time, a synchronization construct a bit more sophisticated than a single Mutex might yield better performance.
In your code, it looks like not only does @clients need to be properly synchronized, but so also do all its elements because writing to a socket is a modification.
Related Topics
Detecting If This Is an Iframe Load or Direct
In Ruby, How to Find Out If a String Is Not in an Array
How to Convert Utf8 Combined Characters into Single Utf8 Characters in Ruby
How to Update Rails Locale Yaml File Without Loosing Comments and Variables
Ruby: Is Variable Is Object in Ruby
Lion Osx: How to Install Libfreetype.6.Dylib
Understanding Ruby Splat in Ranges and Arrays
Semifixed: Missing 'Secret_Key_Base' for 'Production' Environment
Recovering from a Broken Tcp Socket in Ruby When in Gets()
Ruby on Rails Country/State Select Enigma
Is There a Bug in Ruby Lookbehind Assertions (1.9/2.0)
Ftps (Tls/Ssl) from Ruby on Rails App
Paperclip File Not Found Error
Commonmarker Gem Cannot Be Installed (Needed for Jekyll) MACos
How to Update to Ruby 2.1.2 Using Rails 3.2.3
No Implicit Conversion from Nil to Integer - When Trying to Add Anything to Array