Thread: Behavior of shared/exclusive row locks

Behavior of shared/exclusive row locks

From
Tom Lane
Date:
I've been reviewing Alvaro's patch for shared row locks, and come across
some corner cases that may need discussion.  Does anyone disagree with
the following statements?

1. If several transactions are holding shared lock on a row, and one
of them wants to actually modify the row (or upgrade its lock to
exclusive), it must wait for the others to end but can then do so.
(I think the patch does this properly, but it's not documented in
the comments.)

2. If a transaction is holding exclusive lock on a row, but then issues
SELECT ... FOR SHARE on the row, its lock should remain exclusive.
(I don't think the patch gets this right.)

Another issue that we may need to think about is that there is no
protection against starvation: a would-be acquirer of a row lock
could wait forever, because there isn't any mechanism preventing
other processes from getting in front of him.  This has always 
been true of our SELECT FOR UPDATE code, but I think the risk is
much higher with SELECT FOR SHARE added to the mix --- one could
easily imagine an endless stream of share-lockers coming along and
effectively handing down the share lock, while a would-be exclusive
locker remains frozen out.  I don't see any easy solution to this,
unfortunately.
        regards, tom lane


Re: Behavior of shared/exclusive row locks

From
Alvaro Herrera
Date:
On Wed, Apr 27, 2005 at 11:19:34AM -0400, Tom Lane wrote:

> 1. If several transactions are holding shared lock on a row, and one
> of them wants to actually modify the row (or upgrade its lock to
> exclusive), it must wait for the others to end but can then do so.
> (I think the patch does this properly, but it's not documented in
> the comments.)
> 
> 2. If a transaction is holding exclusive lock on a row, but then issues
> SELECT ... FOR SHARE on the row, its lock should remain exclusive.
> (I don't think the patch gets this right.)

I agree with both.  The reason the patch does not deal with either is
because I didn't really think about those cases.  My bad.

> Another issue that we may need to think about is that there is no
> protection against starvation: a would-be acquirer of a row lock
> could wait forever, because there isn't any mechanism preventing
> other processes from getting in front of him.

As I already mentioned in private email, I think this could be handled
via locking the MultiXactId itself, using the regular lock manager, just
before going to sleep.  There's no harm in bloating the lock tables too
much because one backend can sleep on only one MultiXactId.  So, any
would-be locker first gets the MultiXactId and then it sleeps on it, to
be awaken when the exclusive locker finishes.

This requires having an additional bogus relation like XactLockTable ...
I see this as introducing more cruft than I originally thought about
removing with.  (Remember, I originally thought about getting rid of
XactLockTableWait and friends entirely.)

-- 
Alvaro Herrera (<alvherre[@]dcc.uchile.cl>)
"Los dioses no protegen a los insensatos.  Éstos reciben protección de
otros insensatos mejor dotados" (Luis Wu, Mundo Anillo)


Re: Behavior of shared/exclusive row locks

From
Tom Lane
Date:
Alvaro Herrera <alvherre@dcc.uchile.cl> writes:
> On Wed, Apr 27, 2005 at 11:19:34AM -0400, Tom Lane wrote:
>> Another issue that we may need to think about is that there is no
>> protection against starvation: a would-be acquirer of a row lock
>> could wait forever, because there isn't any mechanism preventing
>> other processes from getting in front of him.

> As I already mentioned in private email, I think this could be handled
> via locking the MultiXactId itself, using the regular lock manager, just
> before going to sleep.  There's no harm in bloating the lock tables too
> much because one backend can sleep on only one MultiXactId.  So, any
> would-be locker first gets the MultiXactId and then it sleeps on it, to
> be awaken when the exclusive locker finishes.

Well, I'd like to fix also the starvation cases that can arise from
SELECT FOR UPDATE without any MultiXactId involved --- that is, if there
are multiple backends all trying to lock the same tuple, one of them
could lose repeatedly, perhaps indefinitely if the kernel scheduler
isn't quite fair.

Suppose that we redo the LOCKTAGs per previous discussion (which I would
like to do anyway), so that it is possible to define an lmgr lock on a
particular tuple.  The objection to this was that there could be too
many locks held for lmgr to cope, but your idea above shows the way out.
Once a backend realizes that it's got to wait for a tuple, it releases
the page context lock and then gets the lmgr lock representing the
target tuple, with either a shared or exclusive lock mode as
appropriate.  After it gets that, it waits on the current tuple holder
as in your patch.  After it gets that, it updates the tuple state as
needed, and only then releases the lmgr lock.

A backend that thinks it can join an existing shared locker also has to
get the lmgr lock in the same way, so that it will block if there is
a pending exclusive locker.

This gives us full lmgr semantics for resolving who-should-wake-up-first,
but we have no more than one active lmgr lock per backend --- the bulk
of the state is on disk, as in your patch.  And the "normal" paths where
there is no conflict for a tuple don't have any added overhead, because
we don't have to take out a lock in those paths.
        regards, tom lane


Re: Behavior of shared/exclusive row locks

From
Alvaro Herrera
Date:
On Wed, Apr 27, 2005 at 05:51:47PM -0400, Tom Lane wrote:

> Suppose that we redo the LOCKTAGs per previous discussion (which I would
> like to do anyway), so that it is possible to define an lmgr lock on a
> particular tuple.

Hm.  If you want I can give you the part of the patch that dealt with
changing LOCKTAG.  It's not a big deal but it might save you some
effort.  (On the other hand, if you want to do a wholesale revamp of
LOCKTAG, it will only slow you down.)

> The objection to this was that there could be too
> many locks held for lmgr to cope, but your idea above shows the way out.
> Once a backend realizes that it's got to wait for a tuple, it releases
> the page context lock and then gets the lmgr lock representing the
> target tuple, with either a shared or exclusive lock mode as
> appropriate.  After it gets that, it waits on the current tuple holder
> as in your patch.  After it gets that, it updates the tuple state as
> needed, and only then releases the lmgr lock.
> 
> A backend that thinks it can join an existing shared locker also has to
> get the lmgr lock in the same way, so that it will block if there is
> a pending exclusive locker.

Sounds like an excellent plan to me.

-- 
Alvaro Herrera (<alvherre[@]dcc.uchile.cl>)
Essentially, you're proposing Kevlar shoes as a solution for the problem
that you want to walk around carrying a loaded gun aimed at your foot.
(Tom Lane)