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.
On Jul 13, 1:04 am, Brian <butler.bria...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> 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. Fred
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.
On Jul 12, 8:38 pm, Frederick Cheung
<frederick.che...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
wrote:> On Jul 13, 1:04 am, Brian
<butler.bria...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
>
>
>
>
>
> > 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.
>
> Fred- Hide quoted text -
>
> - Show quoted text -
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
On Jul 12, 9:02 pm, Brian
<butler.bria...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
wrote:> 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.
>
> On Jul 12, 8:38 pm, Frederick Cheung
<frederick.che...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
> wrote:
>
>
>
> > On Jul 13, 1:04 am, Brian
<butler.bria...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
>
> > > 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.
>
> > Fred- Hide quoted text -
>
> > - Show quoted text -- Hide quoted text -
>
> - Show quoted text -
Brian wrote:> 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[...] I doubt that you need explicit locking. Skilful use of transactions should do the trick and be much easier to manage. Best, -- Marnen Laibow-Koser http://www.marnen.org marnen-sbuyVjPbboAdnm+yROfE0A@public.gmane.org -- Posted via http://www.ruby-forum.com/.