John Bachir
John Bachir

Reputation: 22731

How can I lock a set of objects for an operation?

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 bars 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

Answers (3)

BeepDog
BeepDog

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

Pan Thomakos
Pan Thomakos

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

Alex Moore
Alex Moore

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

Related Questions