Locking, and How to Test it

This is a two part question. Which type of locking should I use (optimistic vs. pessimistic) and then how do I account for locking in my tests?

My scenario is essentially the purchase of a unique item where the first person to click "Buy" gets the item and everyone else is out of luck. As a single transaction, I need to determine whether the item has already been purchased; if so provide an error message, otherwise buy it. If I did not lock, race conditions may lead multiple people to think they were succesful (and it would make a mess out of my otherwise simple accounting).

I'm leaning towards optimistic because: 1) my rails app is the only thing touching the DB, 2) I'm trying to remain agnostic on DB software for now, but 3) I don't really want a read-lock. Am I overlooking anything that might push me in the other direction?

For testing, I'm trying to be a good TDD citizen, so exactly what test would cause me to write the logic to handle ActiveRecord::StaleObjectError? (Or if I go with pessimistic, the :lock => true option) Considering the record is located, updated, and saved by the same method I can't think of a good way to actually cause the race condition for a test. But common sense tells me it will happen frequently once I have multiple users interacting with the app.

for optimistic locking,

It can be quite handy to just mock save! and have it throw StaleObjectError. depending on your code you may also be able to do

firstInstance = SomeModel.find 1 secondInstance = SomeModel.find 1 secondInstance.created_at_will_change! #or anything that will make the save not be a no-op secondInstance.save!

#do something with firstInstance (it is now stale)

Pessimistic locking is a little different - not much will change for your code except that a select or a write may just block for a little longer than usual. You should be prepared to handle whatever your database does if it times out getting a lock or detects a deadlock.


Ah, great idea. So in the absence of a mock object framework*, is something like the following fairly standard?

    MyModel.send(:define_method, :save!) { raise ActiveRecord::StaleObjectError, "Boom!" }     ... #test handling of race condition here     MyModel.send(:undef, :save!)

Since "save!" is inherited, I don't think I need to worry about aliasing the old one out of the way or anything.

This technique works well. I'm able to reimplement save (or save!) to simulate pretty much any race condition I want to handle. I ended up with something like this:

  # mock save to simulate another user getting there first.   sneaky_user = users(:brian)   ItemForSale.send(:define_method, :save) do     buyer = sneaky_user     raise ActiveRecord::StaleObjectError, "Boom!"   end

  item = itemsforsale(:vase)   assert !item.sell_to(users(:allen)) # uses save internally   assert item.errors.invalid?(:buyer)

  # models aren't reloaded, so I need to clean up   ItemForSale.send(:undef_method, :save)

Which should work well as a test for this:

def sell_to(user)   buyer = user

  begin     save   rescue ActiveRecord::StaleObjectError     errors.add(:buyer, "already assigned for this item")     return false   end end