Revised Patch to allow multiple table locks in "Unison"

Started by Neil Padgettover 24 years ago53 messageshackers
Jump to latest
#1Neil Padgett
npadgett@redhat.com

Here is the revised patch to allow the syntax:

LOCK a,b,c;

It uses the method I described as the "new" method in a previous
message.

Neil

--
Neil Padgett
Red Hat Canada Ltd. E-Mail: npadgett@redhat.com
2323 Yonge Street, Suite #300,
Toronto, ON M4P 2C9

Index: src/backend/commands/command.c
===================================================================
RCS file:
/home/projects/pgsql/cvsroot/pgsql/src/backend/commands/command.c,v
retrieving revision 1.136
diff -c -p -r1.136 command.c
*** src/backend/commands/command.c	2001/07/16 05:06:57	1.136
--- src/backend/commands/command.c	2001/07/26 00:02:24
*************** needs_toast_table(Relation rel)
*** 1994,2019 ****
  void
  LockTableCommand(LockStmt *lockstmt)
  {
! 	Relation	rel;
! 	int			aclresult;

! rel = heap_openr(lockstmt->relname, NoLock);

! if (rel->rd_rel->relkind != RELKIND_RELATION)
! elog(ERROR, "LOCK TABLE: %s is not a table", lockstmt->relname);

! if (lockstmt->mode == AccessShareLock)
! aclresult = pg_aclcheck(lockstmt->relname, GetUserId(), ACL_SELECT);
! else
! aclresult = pg_aclcheck(lockstmt->relname, GetUserId(),
! ACL_UPDATE | ACL_DELETE);

! if (aclresult != ACLCHECK_OK)
! elog(ERROR, "LOCK TABLE: permission denied");

! LockRelation(rel, lockstmt->mode);

! heap_close(rel, NoLock); /* close rel, keep lock */
}

--- 1994,2170 ----
  void
  LockTableCommand(LockStmt *lockstmt)
  {
! 	int         relCnt;

! relCnt = length(lockstmt -> rellist);

! /* Handle a single relation lock specially to avoid overhead on
likely the
! most common case */

! if(relCnt == 1)
! {

! /* Locking a single table */

! Relation rel;
! int aclresult;
! char *relname;

! relname = strVal(lfirst(lockstmt->rellist));
!
! freeList(lockstmt->rellist);
!
! rel = heap_openr(relname, NoLock);
!
! if (rel->rd_rel->relkind != RELKIND_RELATION)
! elog(ERROR, "LOCK TABLE: %s is not a table", relname);
!
! if (lockstmt->mode == AccessShareLock)
! aclresult = pg_aclcheck(relname, GetUserId(),
! ACL_SELECT);
! else
! aclresult = pg_aclcheck(relname, GetUserId(),
! ACL_UPDATE | ACL_DELETE);
!
! if (aclresult != ACLCHECK_OK)
! elog(ERROR, "LOCK TABLE: permission denied");
!
! LockRelation(rel, lockstmt->mode);
!
! pfree(relname);
!
! heap_close(rel, NoLock); /* close rel, keep lock */
! }
! else
! {
! List *p;
! Relation *relationArray;
! Relation *pRel;
! Relation *blockingLockTarget;
! bool allLocked = false;
!
! /* Locking multiple tables */
!
! /* Create an array of relations */
!
! relationArray = palloc(relCnt * sizeof(Relation));
! blockingLockTarget = relationArray;
!
! pRel = relationArray;
!
! /* Iterate over the list and populate the relation array */
!
! foreach(p, lockstmt->rellist)
! {
! char *relname = strVal(lfirst(p));
! int aclresult;
!
! *pRel = heap_openr(relname, NoLock);
!
! if ((*pRel)->rd_rel->relkind != RELKIND_RELATION)
! elog(ERROR, "LOCK TABLE: %s is not a table",
! relname);
!
! if (lockstmt->mode == AccessShareLock)
! aclresult = pg_aclcheck(relname, GetUserId(),
! ACL_SELECT);
! else
! aclresult = pg_aclcheck(relname, GetUserId(),
! ACL_UPDATE | ACL_DELETE);
!
! if (aclresult != ACLCHECK_OK)
! elog(ERROR, "LOCK TABLE: permission denied");
!
! pRel++;
! pfree(relname);
! }
!
! /* Now acquire locks on all the relations */
!
! while(!allLocked)
! {
!
! allLocked = true;
!
! /* Lock the blocking lock target (initially the first lock
! in the user's list */
!
! LockRelation(*blockingLockTarget, lockstmt->mode);
!
! /* Lock is now obtained on the lock target, now grab locks in a
! non-blocking way on the rest of the list */
!
! for(pRel = blockingLockTarget + 1;
! pRel < relationArray + relCnt;
! pRel++)
! {
! if(!ConditionalLockRelation(*pRel, lockstmt->mode))
! {
! Relation *pRelUnlock;
!
! /* Flag that all relations were not successfully
! locked */
!
! allLocked = false;
!
! /* A lock wasn't obtained, so unlock all others */
!
! for(pRelUnlock = blockingLockTarget;
! pRelUnlock < pRel;
! pRelUnlock++)
! UnlockRelation(*pRelUnlock, lockstmt->mode);
!
! /* Next time, do our blocking lock on the contended lock */
!
! blockingLockTarget = pRel;
!
! /* Now break out and try again */
!
! break;
! }
! }
!
! /* Now, lock anything before the blocking lock target in the lock
! target array, if we were successful in getting locks on
! everything after and including the blocking target */
!
! if(allLocked)
! {
! for(pRel = relationArray;
! pRel < blockingLockTarget;
! pRel++)
! {
! if(!ConditionalLockRelation(*pRel, lockstmt->mode))
! {
! Relation *pRelUnlock;
!
! /* Flag that all relations were not successfully
! locked */
!
! allLocked = false;
!
! /* Lock wasn't obtained, so unlock all others */
!
! for(pRelUnlock = relationArray;
! pRelUnlock < pRel;
! pRelUnlock++)
! UnlockRelation(*pRelUnlock, lockstmt->mode);
!
! /* Next time, do our blocking lock on the contended
! lock */
!
! blockingLockTarget = pRel;
!
! /* Now break out and try again */
!
! break;
! }
! }
! }
! }
!
! pfree(relationArray);
! }
}

Index: src/backend/nodes/copyfuncs.c
===================================================================
RCS file:
/home/projects/pgsql/cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v
retrieving revision 1.148
diff -c -p -r1.148 copyfuncs.c
*** src/backend/nodes/copyfuncs.c	2001/07/16 19:07:37	1.148
--- src/backend/nodes/copyfuncs.c	2001/07/26 00:02:24
*************** _copyLockStmt(LockStmt *from)
*** 2425,2432 ****
  {
  	LockStmt   *newnode = makeNode(LockStmt);

! if (from->relname)
! newnode->relname = pstrdup(from->relname);
newnode->mode = from->mode;

  	return newnode;
--- 2425,2432 ----
  {
  	LockStmt   *newnode = makeNode(LockStmt);

! Node_Copy(from, newnode, rellist);
!
newnode->mode = from->mode;

  	return newnode;
Index: src/backend/nodes/equalfuncs.c
===================================================================
RCS file:
/home/projects/pgsql/cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v
retrieving revision 1.96
diff -c -p -r1.96 equalfuncs.c
*** src/backend/nodes/equalfuncs.c	2001/07/16 19:07:38	1.96
--- src/backend/nodes/equalfuncs.c	2001/07/26 00:02:24
*************** _equalDropUserStmt(DropUserStmt *a, Drop
*** 1283,1289 ****
  static bool
  _equalLockStmt(LockStmt *a, LockStmt *b)
  {
! 	if (!equalstr(a->relname, b->relname))
  		return false;
  	if (a->mode != b->mode)
  		return false;
--- 1283,1289 ----
  static bool
  _equalLockStmt(LockStmt *a, LockStmt *b)
  {
! 	if (!equal(a->rellist, b->rellist))
  		return false;
  	if (a->mode != b->mode)
  		return false;
Index: src/backend/parser/gram.y
===================================================================
RCS file: /home/projects/pgsql/cvsroot/pgsql/src/backend/parser/gram.y,v
retrieving revision 2.238
diff -c -p -r2.238 gram.y
*** src/backend/parser/gram.y	2001/07/16 19:07:40	2.238
--- src/backend/parser/gram.y	2001/07/26 00:02:25
*************** DeleteStmt:  DELETE FROM relation_expr w
*** 3280,3290 ****
  				}
  		;

! LockStmt: LOCK_P opt_table relation_name opt_lock
{
LockStmt *n = makeNode(LockStmt);

! 					n->relname = $3;
  					n->mode = $4;
  					$$ = (Node *)n;
  				}
--- 3280,3290 ----
  				}
  		;

! LockStmt: LOCK_P opt_table relation_name_list opt_lock
{
LockStmt *n = makeNode(LockStmt);

! 					n->rellist = $3;
  					n->mode = $4;
  					$$ = (Node *)n;
  				}
Index: src/include/nodes/parsenodes.h
===================================================================
RCS file:
/home/projects/pgsql/cvsroot/pgsql/src/include/nodes/parsenodes.h,v
retrieving revision 1.136
diff -c -p -r1.136 parsenodes.h
*** src/include/nodes/parsenodes.h	2001/07/16 19:07:40	1.136
--- src/include/nodes/parsenodes.h	2001/07/26 00:02:26
*************** typedef struct VariableResetStmt
*** 760,766 ****
  typedef struct LockStmt
  {
  	NodeTag		type;
! 	char	   *relname;		/* relation to lock */
  	int			mode;			/* lock mode */
  } LockStmt;
--- 760,766 ----
  typedef struct LockStmt
  {
  	NodeTag		type;
! 	List	   *rellist;		/* relation to lock */
  	int			mode;			/* lock mode */
  } LockStmt;
Index: src/interfaces/ecpg/preproc/preproc.y
===================================================================
RCS file:
/home/projects/pgsql/cvsroot/pgsql/src/interfaces/ecpg/preproc/preproc.y,v
retrieving revision 1.146
diff -c -p -r1.146 preproc.y
*** src/interfaces/ecpg/preproc/preproc.y	2001/07/16 05:07:00	1.146
--- src/interfaces/ecpg/preproc/preproc.y	2001/07/26 00:02:27
*************** DeleteStmt:  DELETE FROM relation_expr w
*** 2421,2427 ****
  				}
  		;
! LockStmt:  LOCK_P opt_table relation_name opt_lock
  				{
  					$$ = cat_str(4, make_str("lock"), $2, $3, $4);
  				}
--- 2421,2427 ----
  				}
  		;

! LockStmt: LOCK_P opt_table relation_name_list opt_lock
{
$$ = cat_str(4, make_str("lock"), $2, $3, $4);
}

#2Bruce Momjian
bruce@momjian.us
In reply to: Neil Padgett (#1)
Re: Revised Patch to allow multiple table locks in "Unison"

The problem with this patch is that it doesn't always lock the tables in
the order supplied by the user, leading to possible deadlock. My guess
is that you need to try locking A, B, C and if B hangs, I think you need
to sleep on B, and when you get it, release the lock on B and try A, B,
C again. I know this is a pain and could fail multiple times, but I
think we have to do it this ay.

Here is the revised patch to allow the syntax:

LOCK a,b,c;

It uses the method I described as the "new" method in a previous
message.

Neil

--
Neil Padgett
Red Hat Canada Ltd. E-Mail: npadgett@redhat.com
2323 Yonge Street, Suite #300,
Toronto, ON M4P 2C9

Index: src/backend/commands/command.c
===================================================================
RCS file:
/home/projects/pgsql/cvsroot/pgsql/src/backend/commands/command.c,v
retrieving revision 1.136
diff -c -p -r1.136 command.c
*** src/backend/commands/command.c	2001/07/16 05:06:57	1.136
--- src/backend/commands/command.c	2001/07/26 00:02:24
*************** needs_toast_table(Relation rel)
*** 1994,2019 ****
void
LockTableCommand(LockStmt *lockstmt)
{
! 	Relation	rel;
! 	int			aclresult;

! rel = heap_openr(lockstmt->relname, NoLock);

! if (rel->rd_rel->relkind != RELKIND_RELATION)
! elog(ERROR, "LOCK TABLE: %s is not a table", lockstmt->relname);

! if (lockstmt->mode == AccessShareLock)
! aclresult = pg_aclcheck(lockstmt->relname, GetUserId(), ACL_SELECT);
! else
! aclresult = pg_aclcheck(lockstmt->relname, GetUserId(),
! ACL_UPDATE | ACL_DELETE);

! if (aclresult != ACLCHECK_OK)
! elog(ERROR, "LOCK TABLE: permission denied");

! LockRelation(rel, lockstmt->mode);

! heap_close(rel, NoLock); /* close rel, keep lock */
}

--- 1994,2170 ----
void
LockTableCommand(LockStmt *lockstmt)
{
! 	int         relCnt;

! relCnt = length(lockstmt -> rellist);

! /* Handle a single relation lock specially to avoid overhead on
likely the
! most common case */

! if(relCnt == 1)
! {

! /* Locking a single table */

! Relation rel;
! int aclresult;
! char *relname;

! relname = strVal(lfirst(lockstmt->rellist));
!
! freeList(lockstmt->rellist);
!
! rel = heap_openr(relname, NoLock);
!
! if (rel->rd_rel->relkind != RELKIND_RELATION)
! elog(ERROR, "LOCK TABLE: %s is not a table", relname);
!
! if (lockstmt->mode == AccessShareLock)
! aclresult = pg_aclcheck(relname, GetUserId(),
! ACL_SELECT);
! else
! aclresult = pg_aclcheck(relname, GetUserId(),
! ACL_UPDATE | ACL_DELETE);
!
! if (aclresult != ACLCHECK_OK)
! elog(ERROR, "LOCK TABLE: permission denied");
!
! LockRelation(rel, lockstmt->mode);
!
! pfree(relname);
!
! heap_close(rel, NoLock); /* close rel, keep lock */
! }
! else
! {
! List *p;
! Relation *relationArray;
! Relation *pRel;
! Relation *blockingLockTarget;
! bool allLocked = false;
!
! /* Locking multiple tables */
!
! /* Create an array of relations */
!
! relationArray = palloc(relCnt * sizeof(Relation));
! blockingLockTarget = relationArray;
!
! pRel = relationArray;
!
! /* Iterate over the list and populate the relation array */
!
! foreach(p, lockstmt->rellist)
! {
! char *relname = strVal(lfirst(p));
! int aclresult;
!
! *pRel = heap_openr(relname, NoLock);
!
! if ((*pRel)->rd_rel->relkind != RELKIND_RELATION)
! elog(ERROR, "LOCK TABLE: %s is not a table",
! relname);
!
! if (lockstmt->mode == AccessShareLock)
! aclresult = pg_aclcheck(relname, GetUserId(),
! ACL_SELECT);
! else
! aclresult = pg_aclcheck(relname, GetUserId(),
! ACL_UPDATE | ACL_DELETE);
!
! if (aclresult != ACLCHECK_OK)
! elog(ERROR, "LOCK TABLE: permission denied");
!
! pRel++;
! pfree(relname);
! }
!
! /* Now acquire locks on all the relations */
!
! while(!allLocked)
! {
!
! allLocked = true;
!
! /* Lock the blocking lock target (initially the first lock
! in the user's list */
!
! LockRelation(*blockingLockTarget, lockstmt->mode);
!
! /* Lock is now obtained on the lock target, now grab locks in a
! non-blocking way on the rest of the list */
!
! for(pRel = blockingLockTarget + 1;
! pRel < relationArray + relCnt;
! pRel++)
! {
! if(!ConditionalLockRelation(*pRel, lockstmt->mode))
! {
! Relation *pRelUnlock;
!
! /* Flag that all relations were not successfully
! locked */
!
! allLocked = false;
!
! /* A lock wasn't obtained, so unlock all others */
!
! for(pRelUnlock = blockingLockTarget;
! pRelUnlock < pRel;
! pRelUnlock++)
! UnlockRelation(*pRelUnlock, lockstmt->mode);
!
! /* Next time, do our blocking lock on the contended lock */
!
! blockingLockTarget = pRel;
!
! /* Now break out and try again */
!
! break;
! }
! }
!
! /* Now, lock anything before the blocking lock target in the lock
! target array, if we were successful in getting locks on
! everything after and including the blocking target */
!
! if(allLocked)
! {
! for(pRel = relationArray;
! pRel < blockingLockTarget;
! pRel++)
! {
! if(!ConditionalLockRelation(*pRel, lockstmt->mode))
! {
! Relation *pRelUnlock;
!
! /* Flag that all relations were not successfully
! locked */
!
! allLocked = false;
!
! /* Lock wasn't obtained, so unlock all others */
!
! for(pRelUnlock = relationArray;
! pRelUnlock < pRel;
! pRelUnlock++)
! UnlockRelation(*pRelUnlock, lockstmt->mode);
!
! /* Next time, do our blocking lock on the contended
! lock */
!
! blockingLockTarget = pRel;
!
! /* Now break out and try again */
!
! break;
! }
! }
! }
! }
!
! pfree(relationArray);
! }
}

Index: src/backend/nodes/copyfuncs.c
===================================================================
RCS file:
/home/projects/pgsql/cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v
retrieving revision 1.148
diff -c -p -r1.148 copyfuncs.c
*** src/backend/nodes/copyfuncs.c	2001/07/16 19:07:37	1.148
--- src/backend/nodes/copyfuncs.c	2001/07/26 00:02:24
*************** _copyLockStmt(LockStmt *from)
*** 2425,2432 ****
{
LockStmt   *newnode = makeNode(LockStmt);

! if (from->relname)
! newnode->relname = pstrdup(from->relname);
newnode->mode = from->mode;

return newnode;
--- 2425,2432 ----
{
LockStmt   *newnode = makeNode(LockStmt);

! Node_Copy(from, newnode, rellist);
!
newnode->mode = from->mode;

return newnode;
Index: src/backend/nodes/equalfuncs.c
===================================================================
RCS file:
/home/projects/pgsql/cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v
retrieving revision 1.96
diff -c -p -r1.96 equalfuncs.c
*** src/backend/nodes/equalfuncs.c	2001/07/16 19:07:38	1.96
--- src/backend/nodes/equalfuncs.c	2001/07/26 00:02:24
*************** _equalDropUserStmt(DropUserStmt *a, Drop
*** 1283,1289 ****
static bool
_equalLockStmt(LockStmt *a, LockStmt *b)
{
! 	if (!equalstr(a->relname, b->relname))
return false;
if (a->mode != b->mode)
return false;
--- 1283,1289 ----
static bool
_equalLockStmt(LockStmt *a, LockStmt *b)
{
! 	if (!equal(a->rellist, b->rellist))
return false;
if (a->mode != b->mode)
return false;
Index: src/backend/parser/gram.y
===================================================================
RCS file: /home/projects/pgsql/cvsroot/pgsql/src/backend/parser/gram.y,v
retrieving revision 2.238
diff -c -p -r2.238 gram.y
*** src/backend/parser/gram.y	2001/07/16 19:07:40	2.238
--- src/backend/parser/gram.y	2001/07/26 00:02:25
*************** DeleteStmt:  DELETE FROM relation_expr w
*** 3280,3290 ****
}
;

! LockStmt: LOCK_P opt_table relation_name opt_lock
{
LockStmt *n = makeNode(LockStmt);

! 					n->relname = $3;
n->mode = $4;
$$ = (Node *)n;
}
--- 3280,3290 ----
}
;

! LockStmt: LOCK_P opt_table relation_name_list opt_lock
{
LockStmt *n = makeNode(LockStmt);

! 					n->rellist = $3;
n->mode = $4;
$$ = (Node *)n;
}
Index: src/include/nodes/parsenodes.h
===================================================================
RCS file:
/home/projects/pgsql/cvsroot/pgsql/src/include/nodes/parsenodes.h,v
retrieving revision 1.136
diff -c -p -r1.136 parsenodes.h
*** src/include/nodes/parsenodes.h	2001/07/16 19:07:40	1.136
--- src/include/nodes/parsenodes.h	2001/07/26 00:02:26
*************** typedef struct VariableResetStmt
*** 760,766 ****
typedef struct LockStmt
{
NodeTag		type;
! 	char	   *relname;		/* relation to lock */
int			mode;			/* lock mode */
} LockStmt;
--- 760,766 ----
typedef struct LockStmt
{
NodeTag		type;
! 	List	   *rellist;		/* relation to lock */
int			mode;			/* lock mode */
} LockStmt;
Index: src/interfaces/ecpg/preproc/preproc.y
===================================================================
RCS file:
/home/projects/pgsql/cvsroot/pgsql/src/interfaces/ecpg/preproc/preproc.y,v
retrieving revision 1.146
diff -c -p -r1.146 preproc.y
*** src/interfaces/ecpg/preproc/preproc.y	2001/07/16 05:07:00	1.146
--- src/interfaces/ecpg/preproc/preproc.y	2001/07/26 00:02:27
*************** DeleteStmt:  DELETE FROM relation_expr w
*** 2421,2427 ****
}
;
! LockStmt:  LOCK_P opt_table relation_name opt_lock
{
$$ = cat_str(4, make_str("lock"), $2, $3, $4);
}
--- 2421,2427 ----
}
;

! LockStmt: LOCK_P opt_table relation_name_list opt_lock
{
$$ = cat_str(4, make_str("lock"), $2, $3, $4);
}

---------------------------(end of broadcast)---------------------------
TIP 6: Have you searched our list archives?

http://www.postgresql.org/search.mpl

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026
#3Neil Padgett
npadgett@redhat.com
In reply to: Bruce Momjian (#2)
Re: Revised Patch to allow multiple table locks in "Unison"

Bruce Momjian wrote:

The problem with this patch is that it doesn't always lock the tables in
the order supplied by the user, leading to possible deadlock. My guess
is that you need to try locking A, B, C and if B hangs, I think you need
to sleep on B, and when you get it, release the lock on B and try A, B,
C again. I know this is a pain and could fail multiple times, but I
think we have to do it this ay.

Deadlocks are not possible with this patch. The four conditions needed
for deadlock are (according to Operating Systems: Internals and Design
Principles, 4th Ed. by W. Stallings):

1. Mutual exclusion: Only one process may use a resource at a time.
2. Hold and wait: A process may hold allocated resources while awaiting
assignment of others.
3. No preemption: No resources can be forcibly removed from a process
holding it.
4. Circular wait: A closed chain of processes exists, such that each
process holds at least one resource needed by the next process in the
chain.

For deadlock prevention one needs only to prevent the existence of
at least one of the four conditions.

The patch code never holds any of requested locks, while waiting for a
locked relation to become free. If a lock on all tables in the lock list
cannot be acquired at once, it backs off and releases all locks.

Stallings writes about preventing condition 3: "This condition can be
prevented in several ways. [. . .] [One way is to require that,] if a
process holding certain resources is denied a further request, that
process must release its original resources and, if necessary, request
them again together with the additional resources."

This is exactly what the patch does. Observe that if one lock is not
available, the patch releases all locks so far acquired, and then
acquires
the locks again. Hence, condition 3 is prevented, and so deadlock is
prevented.

Neil

p.s. Is this mailing list always this slow?

--
Neil Padgett
Red Hat Canada Ltd. E-Mail: npadgett@redhat.com
2323 Yonge Street, Suite #300,
Toronto, ON M4P 2C9

#4Bruce Momjian
bruce@momjian.us
In reply to: Neil Padgett (#3)
Re: Revised Patch to allow multiple table locks in "Unison"

Bruce Momjian wrote:

The problem with this patch is that it doesn't always lock the tables in
the order supplied by the user, leading to possible deadlock. My guess
is that you need to try locking A, B, C and if B hangs, I think you need
to sleep on B, and when you get it, release the lock on B and try A, B,
C again. I know this is a pain and could fail multiple times, but I
think we have to do it this ay.

Deadlocks are not possible with this patch. The four conditions needed
for deadlock are (according to Operating Systems: Internals and Design
Principles, 4th Ed. by W. Stallings):

...

The patch code never holds any of requested locks, while waiting for a
locked relation to become free. If a lock on all tables in the lock list
cannot be acquired at once, it backs off and releases all locks.

Stallings writes about preventing condition 3: "This condition can be
prevented in several ways. [. . .] [One way is to require that,] if a
process holding certain resources is denied a further request, that
process must release its original resources and, if necessary, request
them again together with the additional resources."

This is exactly what the patch does. Observe that if one lock is not
available, the patch releases all locks so far acquired, and then
acquires
the locks again. Hence, condition 3 is prevented, and so deadlock is
prevented.

Excellent point. I had not considered the fact that you don't hang
waiting for the other locks; you just release them all and try again.

Looks like a great patch, and it seems better than the OID patch in many
ways.

p.s. Is this mailing list always this slow?

Not sure. I have gotten patches stuck in the patches queue recently.
Not sure on the cause. Marc may know.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026
#5Bruce Momjian
bruce@momjian.us
In reply to: Neil Padgett (#3)
Re: Revised Patch to allow multiple table locks in "Unison"

Your patch has been added to the PostgreSQL unapplied patches list at:

http://candle.pha.pa.us/cgi-bin/pgpatches

I will try to apply it within the next 48 hours.

Bruce Momjian wrote:

The problem with this patch is that it doesn't always lock the tables in
the order supplied by the user, leading to possible deadlock. My guess
is that you need to try locking A, B, C and if B hangs, I think you need
to sleep on B, and when you get it, release the lock on B and try A, B,
C again. I know this is a pain and could fail multiple times, but I
think we have to do it this ay.

Deadlocks are not possible with this patch. The four conditions needed
for deadlock are (according to Operating Systems: Internals and Design
Principles, 4th Ed. by W. Stallings):

1. Mutual exclusion: Only one process may use a resource at a time.
2. Hold and wait: A process may hold allocated resources while awaiting
assignment of others.
3. No preemption: No resources can be forcibly removed from a process
holding it.
4. Circular wait: A closed chain of processes exists, such that each
process holds at least one resource needed by the next process in the
chain.

For deadlock prevention one needs only to prevent the existence of
at least one of the four conditions.

The patch code never holds any of requested locks, while waiting for a
locked relation to become free. If a lock on all tables in the lock list
cannot be acquired at once, it backs off and releases all locks.

Stallings writes about preventing condition 3: "This condition can be
prevented in several ways. [. . .] [One way is to require that,] if a
process holding certain resources is denied a further request, that
process must release its original resources and, if necessary, request
them again together with the additional resources."

This is exactly what the patch does. Observe that if one lock is not
available, the patch releases all locks so far acquired, and then
acquires
the locks again. Hence, condition 3 is prevented, and so deadlock is
prevented.

Neil

p.s. Is this mailing list always this slow?

--
Neil Padgett
Red Hat Canada Ltd. E-Mail: npadgett@redhat.com
2323 Yonge Street, Suite #300,
Toronto, ON M4P 2C9

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026
#6Hiroshi Inoue
Inoue@tpf.co.jp
In reply to: Bruce Momjian (#4)
Re: Revised Patch to allow multiple table locks in "Unison"

Bruce Momjian wrote:

Bruce Momjian wrote:

[snip]

Deadlocks are not possible with this patch. The four conditions needed
for deadlock are (according to Operating Systems: Internals and Design
Principles, 4th Ed. by W. Stallings):

...

The patch code never holds any of requested locks, while waiting for a
locked relation to become free. If a lock on all tables in the lock list
cannot be acquired at once, it backs off and releases all locks.

Stallings writes about preventing condition 3: "This condition can be
prevented in several ways. [. . .] [One way is to require that,] if a
process holding certain resources is denied a further request, that
process must release its original resources and, if necessary, request
them again together with the additional resources."

This is exactly what the patch does. Observe that if one lock is not
available, the patch releases all locks so far acquired, and then
acquires
the locks again. Hence, condition 3 is prevented, and so deadlock is
prevented.

Excellent point. I had not considered the fact that you don't hang
waiting for the other locks; you just release them all and try again.

I have a question.
What will happen when the second table is locked for a long time
though the first table isn't locked ?

regards,
Hiroshi Ioue

#7Neil Padgett
npadgett@redhat.com
In reply to: Hiroshi Inoue (#6)
Re: Revised Patch to allow multiple table locks in "Unison"

On Mon, 30 Jul 2001, Hiroshi Inoue wrote:

I have a question.
What will happen when the second table is locked for a long time
though the first table isn't locked ?

Consider the case:

LOCK a,b;

Assume a is free (i.e. not locked), but b is busy (i.e. locked).

First the system will do a blocking lock attempt on a, which will return
immediately, since a was free. Table a is now locked. Now, the system will
try a non-blocking lock on b. But, b is busy so the lock attempt will fail
immediately (since the lock attempt was non-blocking). So, the system will
back off, and the lock on a is released.

Next, a blocking lock attempt will be made on b. (Since it was busy last
time, we want to wait for it to become free.) The lock call will block
until b becomes free. At that time, the lock attempt will return, and b
will be locked. Then, a non-blocking lock attempt will be made on table a.
(Recall that we don't have a lock on it, since we released it during
back-off earlier.) Assuming a is still free, it will be locked and the
LOCK command will complete. Otherwise, if a is busy, the lock attempt will
then restart with a blocking lock attempt on a. The procedure will
continue until all tables are free to lock.

In summary, no locks are held while waiting for tables to become free --
in essence, the tables are locked all at once, once all tables in the
LOCK statement are free.

Neil

--
Neil Padgett
Red Hat Canada Ltd. E-Mail: npadgett@redhat.com
2323 Yonge Street, Suite #300,
Toronto, ON M4P 2C9

#8Bruce Momjian
bruce@momjian.us
In reply to: Neil Padgett (#7)
Re: Revised Patch to allow multiple table locks in "Unison"

On Mon, 30 Jul 2001, Hiroshi Inoue wrote:

I have a question.
What will happen when the second table is locked for a long time
though the first table isn't locked ?

Consider the case:

LOCK a,b;

Assume a is free (i.e. not locked), but b is busy (i.e. locked).

First the system will do a blocking lock attempt on a, which will return
immediately, since a was free. Table a is now locked. Now, the system will
try a non-blocking lock on b. But, b is busy so the lock attempt will fail
immediately (since the lock attempt was non-blocking). So, the system will
back off, and the lock on a is released.

Next, a blocking lock attempt will be made on b. (Since it was busy last
time, we want to wait for it to become free.) The lock call will block
until b becomes free. At that time, the lock attempt will return, and b
will be locked. Then, a non-blocking lock attempt will be made on table a.
(Recall that we don't have a lock on it, since we released it during
back-off earlier.) Assuming a is still free, it will be locked and the
LOCK command will complete. Otherwise, if a is busy, the lock attempt will
then restart with a blocking lock attempt on a. The procedure will
continue until all tables are free to lock.

In summary, no locks are held while waiting for tables to become free --
in essence, the tables are locked all at once, once all tables in the
LOCK statement are free.

The more I think about it the more I like it. I never would have
thought of the idea myself.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026
#9Tom Lane
tgl@sss.pgh.pa.us
In reply to: Hiroshi Inoue (#6)
Re: Revised Patch to allow multiple table locks in "Unison"

Stallings writes about preventing condition 3: "This condition can be
prevented in several ways. [. . .] [One way is to require that,] if a
process holding certain resources is denied a further request, that
process must release its original resources and, if necessary, request
them again together with the additional resources."

This is exactly what the patch does.

No, that is not what it does. Note that Stallings specifies that the
failed requestor must release ALL previously held resources. That is
not feasible in Postgres; some of the locks may be held due to previous
commands in the same transaction. Consider this scenario:

Process 1 Process 2

LOCK a; LOCK b;
... ...
LOCK b,c; LOCK a,c;

The second LOCK statements cannot release the locks already held,
therefore this is a deadlock. While that's no worse than we had
before, I believe that your patch introduces a possibility of
undetected deadlock. Consider this:

Process 1 Process 2

LOCK a,b; LOCK b,a;

A possible interleaving of execution is: 1 acquires lock a, 2 acquires
b, 1 tries to acquire b and fails, 2 tries to acquire a and fails,
1 releases a, 2 releases b, 1 acquires b, 2 acquires a, 1 tries to
acquire a and fails, etc etc. It's implausible that this condition
would persist in perfect lockstep for very long on a uniprocessor
machine ... but not so implausible if you have dual CPUs, each running
one of the two processes at exactly the same speed.

I haven't quite managed to work out a full scenario, but I think it is
possible that the combination of these two effects could result in an
indefinite, never-detected deadlock --- without implausible assumptions
about process speed. It'd probably take three or more processes
contending for several locks, but it seems possible to construct a
failure scenario.

I think that the only safe way to do something like this is to teach
the lock manager itself about multiple simultaneous requests.

regards, tom lane

#10Hiroshi Inoue
Inoue@tpf.co.jp
In reply to: Neil Padgett (#7)
Re: Revised Patch to allow multiple table locks in "Unison"

Neil Padgett wrote:

On Mon, 30 Jul 2001, Hiroshi Inoue wrote:

I have a question.
What will happen when the second table is locked for a long time
though the first table isn't locked ?

Consider the case:

LOCK a,b;

Assume a is free (i.e. not locked), but b is busy (i.e. locked).

First the system will do a blocking lock attempt on a, which will return
immediately, since a was free. Table a is now locked. Now, the system will
try a non-blocking lock on b. But, b is busy so the lock attempt will fail
immediately (since the lock attempt was non-blocking). So, the system will
back off, and the lock on a is released.

Next, a blocking lock attempt will be made on b. (Since it was busy last
time, we want to wait for it to become free.) The lock call will block
until b becomes free. At that time, the lock attempt will return, and b
will be locked. Then, a non-blocking lock attempt will be made on table a.

Is it paranoid to worry about the followings ?

1) Concurrent 'lock table a, b;' and 'lock table b, a;'
could last forever in theory ?
2) 'Lock table a,b' could hardly acquire the lock when
both the table a and b are very frequently accessed.

regards,
Hiroshi Inoue

#11Bruce Momjian
bruce@momjian.us
In reply to: Hiroshi Inoue (#10)
Re: Revised Patch to allow multiple table locks in "Unison"

First the system will do a blocking lock attempt on a, which will return
immediately, since a was free. Table a is now locked. Now, the system will
try a non-blocking lock on b. But, b is busy so the lock attempt will fail
immediately (since the lock attempt was non-blocking). So, the system will
back off, and the lock on a is released.

Next, a blocking lock attempt will be made on b. (Since it was busy last
time, we want to wait for it to become free.) The lock call will block
until b becomes free. At that time, the lock attempt will return, and b
will be locked. Then, a non-blocking lock attempt will be made on table a.

Is it paranoid to worry about the followings ?

1) Concurrent 'lock table a, b;' and 'lock table b, a;'
could last forever in theory ?
2) 'Lock table a,b' could hardly acquire the lock when
both the table a and b are very frequently accessed.

Well, we do tell people to lock things in the same order. If they did
this with two lock statements, they would cause a deadlock. In this
case, they could grab their first lock at the same time, fail on the
second, and wait on the second, get it, fail on the first, and go all
over again. However, they would have to stay synchronized to do this.
If one got out of step it would stop.

Actually, with this new code, we could go back to locking in oid order,
which would eliminate the problem. However, I prefer to do things in
the order specified, at least on the first lock try.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026
#12Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#11)
Re: Revised Patch to allow multiple table locks in "Unison"

Bruce Momjian <pgman@candle.pha.pa.us> writes:

Actually, with this new code, we could go back to locking in oid order,
which would eliminate the problem.

No it wouldn't. If anything, locking in a *randomized* order would be
the best bet. But I have no confidence in this approach, anyway.

regards, tom lane

#13Fernando Nasser
fnasser@redhat.com
In reply to: Bruce Momjian (#4)
Re: Revised Patch to allow multiple table locks in "Unison"

Tom Lane wrote:

Stallings writes about preventing condition 3: "This condition can be
prevented in several ways. [. . .] [One way is to require that,] if a
process holding certain resources is denied a further request, that
process must release its original resources and, if necessary, request
them again together with the additional resources."

This is exactly what the patch does.

No, that is not what it does. Note that Stallings specifies that the
failed requestor must release ALL previously held resources. That is
not feasible in Postgres; some of the locks may be held due to previous
commands in the same transaction. Consider this scenario:

Process 1 Process 2

LOCK a; LOCK b;
... ...
LOCK b,c; LOCK a,c;

The second LOCK statements cannot release the locks already held,
therefore this is a deadlock.

But that is a programmer's error. He/she is acquiring the locks in
reversed order. The fact that the second lock request is in a multiple
lock is not much different from doing it with a single lock like in:

Process 1 Process 2

LOCK a; LOCK b;
... ...
LOCK b; LOCK a;

I guess you've mentioned this scenario because of the undetected
deadlock
concerns, right?

While that's no worse than we had
before, I believe that your patch introduces a possibility of
undetected deadlock. Consider this:

Process 1 Process 2

LOCK a,b; LOCK b,a;

A possible interleaving of execution is: 1 acquires lock a, 2 acquires
b, 1 tries to acquire b and fails, 2 tries to acquire a and fails,
1 releases a, 2 releases b, 1 acquires b, 2 acquires a, 1 tries to
acquire a and fails, etc etc. It's implausible that this condition
would persist in perfect lockstep for very long on a uniprocessor
machine ... but not so implausible if you have dual CPUs, each running
one of the two processes at exactly the same speed.

I haven't quite managed to work out a full scenario, but I think it is
possible that the combination of these two effects could result in an
indefinite, never-detected deadlock --- without implausible assumptions
about process speed.

I believe the undetected deadlock is not possible. To get the multiple
lock statement involved in the deadlock scenario, at least one of the
locks
desired must have been acquired (in a separate statement) by the other
process.

The key property of the algorithm that prevents the undetected deadlocks
is
that it waits on the last failed-to-acquire lock.

As the algorithm waits on the last non-obtained lock and restarts
from there (in a circular fashion), it will eventually reach the lock
that
the other process has and then stop for good (thus allowing the deadlock
detection to see it).

Even if the algorithm started always from the first specified lock and
then
got in the lockstep mode you've described, the (potential) deadlock
would
not be detected because it had not happened yet. It will only happen
when
the 2nd situation you've described ceases to exist and the crossed locks
are
attempted. But them the processes are really stopped and the deadlock
can be detected.

--
Fernando Nasser
Red Hat Canada Ltd. E-Mail: fnasser@redhat.com
2323 Yonge Street, Suite #300
Toronto, Ontario M4P 2C9

#14Fernando Nasser
fnasser@redhat.com
In reply to: Neil Padgett (#7)
Re: Revised Patch to allow multiple table locks in "Unison"

Hiroshi Inoue wrote:

Is it paranoid to worry about the followings ?

1) Concurrent 'lock table a, b;' and 'lock table b, a;'
could last forever in theory ?

You would need a very evil timeslice duration on a single processor, but
it
could happen on a dual processor. However, the two processes would have
to
be synchronized in a very narrow window of instructions, the schedulers
in
both machines would have to be precisely synchronized and absolutely no
interruption (that is not common to both machines) could never occur.
Even a keyboard press will break the enchantment.

I guess it is what we call "unstable equilibrium", possible in theory
but
never happens in practice except for an infinitesimal amount of time.
It is trying to make an egg stand on one end or something like that
(without breaking the egg, of course :-) ).

2) 'Lock table a,b' could hardly acquire the lock when
both the table a and b are very frequently accessed.

Yes, multiple locks with the back off is less aggressive than obtaining
and holding the locks (with individual lock commands).

--
Fernando Nasser
Red Hat Canada Ltd. E-Mail: fnasser@redhat.com
2323 Yonge Street, Suite #300
Toronto, Ontario M4P 2C9

#15Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#12)
Re: Revised Patch to allow multiple table locks in "Unison"

Bruce Momjian <pgman@candle.pha.pa.us> writes:

Actually, with this new code, we could go back to locking in oid order,
which would eliminate the problem.

No it wouldn't. If anything, locking in a *randomized* order would be
the best bet. But I have no confidence in this approach, anyway.

I am looking for a way to get this in there without munging the lock
code, which is already quite complex. What about doing some sort of
small sleep after we reset back to the beginning of the table list.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026
#16Bruce Momjian
bruce@momjian.us
In reply to: Fernando Nasser (#13)
Re: Revised Patch to allow multiple table locks in "Unison"

The second LOCK statements cannot release the locks already held,
therefore this is a deadlock.

But that is a programmer's error. He/she is acquiring the locks in
reversed order. The fact that the second lock request is in a multiple
lock is not much different from doing it with a single lock like in:

Process 1 Process 2

LOCK a; LOCK b;
... ...
LOCK b; LOCK a;

I guess you've mentioned this scenario because of the undetected
deadlock
concerns, right?

I guess so. The above would issue a deadlock error message, while the
LOCK a,b would just spin around.

While that's no worse than we had
before, I believe that your patch introduces a possibility of
undetected deadlock. Consider this:

Process 1 Process 2

LOCK a,b; LOCK b,a;

A possible interleaving of execution is: 1 acquires lock a, 2 acquires
b, 1 tries to acquire b and fails, 2 tries to acquire a and fails,
1 releases a, 2 releases b, 1 acquires b, 2 acquires a, 1 tries to
acquire a and fails, etc etc. It's implausible that this condition
would persist in perfect lockstep for very long on a uniprocessor
machine ... but not so implausible if you have dual CPUs, each running
one of the two processes at exactly the same speed.

I haven't quite managed to work out a full scenario, but I think it is
possible that the combination of these two effects could result in an
indefinite, never-detected deadlock --- without implausible assumptions
about process speed.

I believe the undetected deadlock is not possible. To get the multiple
lock statement involved in the deadlock scenario, at least one of the
locks
desired must have been acquired (in a separate statement) by the other
process.

The key property of the algorithm that prevents the undetected deadlocks
is
that it waits on the last failed-to-acquire lock.

As the algorithm waits on the last non-obtained lock and restarts
from there (in a circular fashion), it will eventually reach the lock
that
the other process has and then stop for good (thus allowing the deadlock
detection to see it).

Even if the algorithm started always from the first specified lock and
then
got in the lockstep mode you've described, the (potential) deadlock
would
not be detected because it had not happened yet. It will only happen
when
the 2nd situation you've described ceases to exist and the crossed locks
are
attempted. But them the processes are really stopped and the deadlock
can be detected.

The unusual case here is that deadlock is not checked on request, but
only after waiting on the lock for a while. This is because deadlock
detection is an expensive operation. In fact, you don't want deadlock
detection in this case because LOCK a,b could be evaluated as a,b or b,a
and you don't want it to fail randomly with deadlock messages.

I certainly would like to find a solution to this that makes everyone
comfortable.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026
#17Neil Padgett
npadgett@redhat.com
In reply to: Bruce Momjian (#15)
Re: Revised Patch to allow multiple table locks in "Unison"

On Mon, 30 Jul 2001, Bruce Momjian wrote:

Bruce Momjian <pgman@candle.pha.pa.us> writes:

Actually, with this new code, we could go back to locking in oid order,
which would eliminate the problem.

No it wouldn't. If anything, locking in a *randomized* order would be
the best bet. But I have no confidence in this approach, anyway.

I am looking for a way to get this in there without munging the lock
code, which is already quite complex. What about doing some sort of
small sleep after we reset back to the beginning of the table list.

It seems to me that we already have a small sleep in place. After all, in
order to acquite a lock, the shared memory area has to be accessed. So,
the contenders for a lock both have to go through a spin lock. So, if we
have the two "stuck" processes as in Tom's example, one will win at
acquiring the spin lock and the other will have to wait. So, they become
desynchronized, regardless of how many CPUs or what memory architecture
you have.

Neil

--
Neil Padgett
Red Hat Canada Ltd. E-Mail: npadgett@redhat.com
2323 Yonge Street, Suite #300,
Toronto, ON M4P 2C9

#18Bruce Momjian
bruce@momjian.us
In reply to: Neil Padgett (#17)
Re: Revised Patch to allow multiple table locks in "Unison"

On Mon, 30 Jul 2001, Bruce Momjian wrote:

Bruce Momjian <pgman@candle.pha.pa.us> writes:

Actually, with this new code, we could go back to locking in oid order,
which would eliminate the problem.

No it wouldn't. If anything, locking in a *randomized* order would be
the best bet. But I have no confidence in this approach, anyway.

I am looking for a way to get this in there without munging the lock
code, which is already quite complex. What about doing some sort of
small sleep after we reset back to the beginning of the table list.

It seems to me that we already have a small sleep in place. After all, in
order to acquite a lock, the shared memory area has to be accessed. So,
the contenders for a lock both have to go through a spin lock. So, if we
have the two "stuck" processes as in Tom's example, one will win at
acquiring the spin lock and the other will have to wait. So, they become
desynchronized, regardless of how many CPUs or what memory architecture
you have.

I see your point now, that they can't synchronize because they have to
go through the same semaphore and therefore get out of sync. Do they
get out of sync enough for one to get the lock while the other is not
holding it, or do the locks actually keep them in sync? I don't know
the answer.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026
#19Fernando Nasser
fnasser@redhat.com
In reply to: Bruce Momjian (#16)
Re: Revised Patch to allow multiple table locks in "Unison"

Bruce Momjian wrote:

The unusual case here is that deadlock is not checked on request, but
only after waiting on the lock for a while. This is because deadlock
detection is an expensive operation.

But that is what happens. If one of the locks is not obtained, the
algorithm does wait on that lock (after releasing the other locks).
In the case of a deadlock (tom's scenario #1) it would wait forever,
but the deadlock detection will find it in there and break it.

In fact, you don't want deadlock
detection in this case because LOCK a,b could be evaluated as a,b or b,a
and you don't want it to fail randomly with deadlock messages.

It does not change the deadlock scenario at all. It is still determined
by the resources in a previous (independent) LOCK statement and the ones
on
this LOCK statement (being it multiple or not) to be crossed.

And deadlock failures will be intermittent anyway. A potential deadlock
condition may or may not happen each time depending on the interleaving
of
execution of the two processes.

--
Fernando Nasser
Red Hat Canada Ltd. E-Mail: fnasser@redhat.com
2323 Yonge Street, Suite #300
Toronto, Ontario M4P 2C9

#20Bruce Momjian
bruce@momjian.us
In reply to: Fernando Nasser (#19)
Re: Revised Patch to allow multiple table locks in "Unison"

Bruce Momjian wrote:

The unusual case here is that deadlock is not checked on request, but
only after waiting on the lock for a while. This is because deadlock
detection is an expensive operation.

But that is what happens. If one of the locks is not obtained, the
algorithm does wait on that lock (after releasing the other locks).
In the case of a deadlock (tom's scenario #1) it would wait forever,
but the deadlock detection will find it in there and break it.

OK, I thought you were talking about the LOCK a,b case, not the other
case where we had a previous LOCK statement. Sorry.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026
#21Fernando Nasser
fnasser@redhat.com
In reply to: Bruce Momjian (#18)
#22Bruce Momjian
bruce@momjian.us
In reply to: Fernando Nasser (#21)
#23Tom Lane
tgl@sss.pgh.pa.us
In reply to: Fernando Nasser (#19)
#24Tom Lane
tgl@sss.pgh.pa.us
In reply to: Neil Padgett (#17)
#25Tom Lane
tgl@sss.pgh.pa.us
In reply to: Fernando Nasser (#21)
#26Neil Padgett
npadgett@redhat.com
In reply to: Bruce Momjian (#18)
#27Tom Lane
tgl@sss.pgh.pa.us
In reply to: Neil Padgett (#26)
#28Bruce Momjian
bruce@momjian.us
In reply to: Neil Padgett (#26)
#29Hiroshi Inoue
Inoue@tpf.co.jp
In reply to: Bruce Momjian (#28)
#30Neil Padgett
npadgett@redhat.com
In reply to: Bruce Momjian (#28)
#31Tom Lane
tgl@sss.pgh.pa.us
In reply to: Neil Padgett (#30)
#32Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#31)
#33Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#32)
#34Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#33)
#35Fernando Nasser
fnasser@redhat.com
In reply to: Bruce Momjian (#34)
#36Tom Lane
tgl@sss.pgh.pa.us
In reply to: Fernando Nasser (#35)
#37Neil Padgett
npadgett@redhat.com
In reply to: Bruce Momjian (#34)
#38Fernando Nasser
fnasser@redhat.com
In reply to: Bruce Momjian (#34)
#39Tom Lane
tgl@sss.pgh.pa.us
In reply to: Fernando Nasser (#38)
#40Tom Lane
tgl@sss.pgh.pa.us
In reply to: Neil Padgett (#37)
#41Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#36)
#42Neil Padgett
npadgett@redhat.com
In reply to: Bruce Momjian (#34)
#43Bruce Momjian
bruce@momjian.us
In reply to: Neil Padgett (#37)
#44Fernando Nasser
fnasser@redhat.com
In reply to: Bruce Momjian (#34)
#45Tom Lane
tgl@sss.pgh.pa.us
In reply to: Fernando Nasser (#44)
#46Fernando Nasser
fnasser@redhat.com
In reply to: Bruce Momjian (#34)
#47Tom Lane
tgl@sss.pgh.pa.us
In reply to: Fernando Nasser (#46)
#48Bruce Momjian
bruce@momjian.us
In reply to: Neil Padgett (#42)
#49Tom Lane
tgl@sss.pgh.pa.us
In reply to: Bruce Momjian (#48)
#50Bruce Momjian
bruce@momjian.us
In reply to: Tom Lane (#49)
#51Neil Padgett
npadgett@redhat.com
In reply to: Bruce Momjian (#48)
#52Bruce Momjian
bruce@momjian.us
In reply to: Neil Padgett (#51)
#53Bruce Momjian
bruce@momjian.us
In reply to: Neil Padgett (#51)