Reputation: 22731
In my rails application, I have some code like this:
def foo
if object_bar_exists
raise "can't create bar twice!"
end
Bar.create
end
Which could be invoked by two different requests coming into the application server. If this code is run by two requests simultaneously, and they both run the if
check at the same time, neither will find the other's bar
, and 2 bar
s will be created.
What's the best way to create a "mutex" for "the collection of bars"? A special purpose mutex table in the DB?
update
I should emphasize that I cannot use a memory mutex here, because the concurrency is across requests/processes and not threads.
Upvotes: 5
Views: 3944
Reputation: 5075
I would probably create a Singleton object in the lib directory, so that you only have one instance of the thing, and use Mutexes to lock on it.
This way, you can ensure only one access to the thing at any given point in time. Of course, any other requests will block on it, so that's something to keep in mind.
For multiple machines, you'd have to store a token in a database, and synchronize some kind of access to the token. Like, the token has to be queried and pulled out, and keep track of some number or something to ensure that people cannot remove the token at the same time. Or use a centralized locking web-service so that your token handling is only in one spot.
Upvotes: 0
Reputation: 34350
The best thing to do is perform your operations in a DB transaction. Because you will probably eventually have multiple applications running and they very possibly won't share memory, you won't be able to create a Mutex lock on the application level, especially if those two application services are running on entirely different physical boxes. Here's how to accomplish the DB transaction:
ActiveRecord::Base.transaction do
# Transaction code goes here.
end
If you want to ensure a rollback on the DB transaction then you'll have to have validation enabled on the Bar class so that an invalid save request will cause a rollback:
ActiveRecord::Base.transaction do
bar = Bar.new(params[:bar])
bar.save!
end
If you already have a bar object in the DB, you can lock that object pessimistically like this:
ActiveRecord::Base.transaction do
bar = Bar.find(1, :lock => true)
# perform operations on bar
end
Upvotes: 6
Reputation: 3455
If they all the requests are coming into the same machine, and the same ruby virtual machine, you could use Ruby's built in Mutex class: Mutex Docs.
If there are multiple machines or rvms, you will have to use a database transaction to create / get the Bar object, assuming it's stored in the db somehow.
Upvotes: 1