"Kevin Grittner" <kevin.gritt...@wicourts.gov> wrote:
 
> I haven't dug into ALTER INDEX enough to know whether it can ever
> cause an index to be rebuilt.  If so, we need to treat it like
> DROP INDEX and REINDEX -- which should change all predicate locks
> of any granularity on the index into relation locks on the
> associated table.
>  
> CLUSTER or an ALTER TABLE which causes a rewrite should change all
> predicate locks on the table and all indexes into relation locks
> on the associated table.  (Obviously, an existing relation lock on
> the table doesn't require any action.)
>  
> TRUNCATE TABLE and DROP TABLE should generate a rw-conflict *in*
> to the enclosing transaction (if it is serializable) from all
> transactions holding predicate locks on the table or its indexes.
> Note that this could cause a transactions which is running one of
> these statements to roll back with a serialization error. This
> seems correct to me, since these operations essentially delete all
> rows.  If you don't want the potential rollback, these operations
> should be run at another isolation level.  The difference between
> these two statements is that I think that TRUNCATE TABLE should
> also move the existing predicate locks to relation locks on the
> table while DROP TABLE (for obvious reasons) should just delete
> the predicate locks.
>  
> DROP DATABASE should quietly clean up any predicate locks from
> committed transactions which haven't yet hit their cleanup point
> because of overlapping transactions in other databases.
 
I missed VACUUM FULL when pulling together the above, but I haven't
found any other omissions.  (Still happy to hear about any that
anyone can spot.)
 
I notice that most of these need to shift transfer locks to relation
locks on the heap, either for a single index or for the heap and all
indexes.  I wrote a function to do this and called it from one place
to be able to test it.  Consider this a WIP patch on which I would
appreciate review while I work on finding the other places to call
it and the miscellaneous other fixes needed.
 
Note that I had to expose one previously-static function from
index.c to find the heap OID from the index OID.  Also, I ran
pgindent against predicate.c, as I generally like to do when I
modify much code, and it found four comment blocks in predicate.c
touched since the recent global pgindent run which it re-wrapped.
I can manually exclude those from the final patch if people would
prefer that; but if people can ignore those whitespace tweaks, it
might not be all bad to get standard formatting onto them at this
point.
 
-Kevin

*** a/src/backend/catalog/index.c
--- b/src/backend/catalog/index.c
***************
*** 54,59 ****
--- 54,60 ----
  #include "parser/parser.h"
  #include "storage/bufmgr.h"
  #include "storage/lmgr.h"
+ #include "storage/predicate.h"
  #include "storage/procarray.h"
  #include "storage/smgr.h"
  #include "utils/builtins.h"
***************
*** 114,120 **** static void validate_index_heapscan(Relation heapRelation,
                                                IndexInfo *indexInfo,
                                                Snapshot snapshot,
                                                v_i_state *state);
- static Oid    IndexGetRelation(Oid indexId);
  static void SetReindexProcessing(Oid heapOid, Oid indexOid);
  static void ResetReindexProcessing(void);
  static void SetReindexPending(List *indexes);
--- 115,120 ----
***************
*** 2721,2727 **** validate_index_heapscan(Relation heapRelation,
   * IndexGetRelation: given an index's relation OID, get the OID of the
   * relation it is an index on.        Uses the system cache.
   */
! static Oid
  IndexGetRelation(Oid indexId)
  {
        HeapTuple       tuple;
--- 2721,2727 ----
   * IndexGetRelation: given an index's relation OID, get the OID of the
   * relation it is an index on.        Uses the system cache.
   */
! Oid
  IndexGetRelation(Oid indexId)
  {
        HeapTuple       tuple;
***************
*** 2782,2787 **** reindex_index(Oid indexId, bool skip_constraint_checks)
--- 2782,2793 ----
         */
        CheckTableNotInUse(iRel, "REINDEX INDEX");
  
+       /*
+        * All predicate locks on the index are about to be made invalid.
+        * Promote them to relation locks on the heap.
+        */
+       TransferPredicateLocksToHeapRelation(iRel);
+ 
        PG_TRY();
        {
                /* Suppress use of the target index while rebuilding it */
*** a/src/backend/storage/lmgr/predicate.c
--- b/src/backend/storage/lmgr/predicate.c
***************
*** 185,190 ****
--- 185,191 ----
  #include "access/twophase.h"
  #include "access/twophase_rmgr.h"
  #include "access/xact.h"
+ #include "catalog/index.h"
  #include "miscadmin.h"
  #include "storage/bufmgr.h"
  #include "storage/predicate.h"
***************
*** 975,982 **** InitPredicateLocks(void)
        bool            found;
  
        /*
!        * Compute size of predicate lock target hashtable.
!        * Note these calculations must agree with PredicateLockShmemSize!
         */
        max_table_size = NPREDICATELOCKTARGETENTS();
  
--- 976,983 ----
        bool            found;
  
        /*
!        * Compute size of predicate lock target hashtable. Note these
!        * calculations must agree with PredicateLockShmemSize!
         */
        max_table_size = NPREDICATELOCKTARGETENTS();
  
***************
*** 1028,1035 **** InitPredicateLocks(void)
                                                                          
hash_flags);
  
        /*
!        * Compute size for serializable transaction hashtable.
!        * Note these calculations must agree with PredicateLockShmemSize!
         */
        max_table_size = (MaxBackends + max_prepared_xacts);
  
--- 1029,1036 ----
                                                                          
hash_flags);
  
        /*
!        * Compute size for serializable transaction hashtable. Note these
!        * calculations must agree with PredicateLockShmemSize!
         */
        max_table_size = (MaxBackends + max_prepared_xacts);
  
***************
*** 2276,2288 **** PredicateLockTupleRowVersionLink(const Relation relation,
                                newxmin;
  
        /*
!        * Bail out quickly if there are no serializable transactions
!        * running.
         *
!        * It's safe to do this check without taking any additional
!        * locks. Even if a serializable transaction starts concurrently,
!        * we know it can't take any SIREAD locks on the modified tuple
!        * because the caller is holding the associated buffer page lock.
         */
        if (!TransactionIdIsValid(PredXact->SxactGlobalXmin))
                return;
--- 2277,2288 ----
                                newxmin;
  
        /*
!        * Bail out quickly if there are no serializable transactions running.
         *
!        * It's safe to do this check without taking any additional locks. Even 
if
!        * a serializable transaction starts concurrently, we know it can't take
!        * any SIREAD locks on the modified tuple because the caller is holding
!        * the associated buffer page lock.
         */
        if (!TransactionIdIsValid(PredXact->SxactGlobalXmin))
                return;
***************
*** 2622,2627 **** exit:
--- 2622,2839 ----
        return !outOfShmem;
  }
  
+ /*
+  * For all connections, transfer all predicate locks for the given relation
+  * to a single relation lock on the heap.  For a heap relation that includes
+  * all locks on indexes; for an index it is just the locks on that one
+  * index.
+  *
+  * This requires grabbing a lot of LW locks and scanning the entire lock
+  * target table for matches.  That makes this more expensive than most
+  * functions, but it will only be called for DDL type commands and there are
+  * fast returns when no serializable transactions are active or the relation
+  * is temporary.
+  *
+  * We are not using the existing TransferPredicateLocksToNewTarget because
+  * it acquires its own locks on the partitions of the two targets invovled,
+  * and we'll already be holding all partition locks.
+  *
+  * We can't throw an error from here, because the call could be from a
+  * transaction which is not serializable.
+  */
+ void
+ TransferPredicateLocksToHeapRelation(const Relation relation)
+ {
+       HASH_SEQ_STATUS seqstat;
+       PREDICATELOCKTARGET *oldtarget;
+       PREDICATELOCKTARGET *heaptarget;
+       PREDICATELOCKTARGETTAG heaptargettag;
+       PREDICATELOCKTAG newpredlocktag;
+       Oid                     dbId;
+       Oid                     indexId;
+       Oid                     heapId;
+       int                     i;
+       bool            isSingleIndex;
+       bool            found;
+       uint32          reservedtargettaghash;
+       uint32          heaptargettaghash;
+ 
+       /*
+        * Bail out quickly if there are no serializable transactions running. 
As
+        * with PredicateLockTupleRowVersionLink, it's safe to check this 
without
+        * taking locks because the caller is holding the buffer page lock.
+        */
+       if (!TransactionIdIsValid(PredXact->SxactGlobalXmin))
+               return;
+ 
+       if (SkipSplitTracking(relation))
+               return;
+ 
+       dbId = relation->rd_node.dbNode;
+       if (relation->rd_index == NULL)
+       {
+               isSingleIndex = false;
+               indexId = InvalidOid;   /* quiet compiler warning */
+               heapId = relation->rd_id;
+       }
+       else
+       {
+               isSingleIndex = true;
+               indexId = relation->rd_id;
+               heapId = relation->rd_index->indrelid;
+       }
+       Assert(heapId != InvalidOid);
+       SET_PREDICATELOCKTARGETTAG_RELATION(heaptargettag,
+                                                                               
dbId,
+                                                                               
heapId);
+       heaptargettaghash = PredicateLockTargetTagHashCode(&heaptargettag);
+       heaptarget = NULL;                      /* Retrieve first time needed, 
then keep. */
+ 
+       LWLockAcquire(SerializablePredicateLockListLock, LW_EXCLUSIVE);
+       for (i = 0; i < NUM_PREDICATELOCK_PARTITIONS; i++)
+               LWLockAcquire(FirstPredicateLockMgrLock + i, LW_EXCLUSIVE);
+       LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
+ 
+       /*
+        * Remove the reserved entry to give us scratch space, so we know we'll 
be
+        * able to create the new lock target.
+        */
+       reservedtargettaghash = 
PredicateLockTargetTagHashCode(&ReservedTargetTag);
+       hash_search_with_hash_value(PredicateLockTargetHash,
+                                                               
&ReservedTargetTag,
+                                                               
reservedtargettaghash,
+                                                               HASH_REMOVE, 
&found);
+       Assert(found);
+ 
+       /* Scan through PredicateLockHash and copy contents */
+       hash_seq_init(&seqstat, PredicateLockTargetHash);
+ 
+       while ((oldtarget = (PREDICATELOCKTARGET *) hash_seq_search(&seqstat)))
+       {
+               PREDICATELOCK *oldpredlock;
+ 
+               /*
+                * Check whether this is a target which needs attention.
+                */
+               if (GET_PREDICATELOCKTARGETTAG_DB(oldtarget->tag) != dbId)
+                       continue;                       /* wrong database */
+               if (isSingleIndex)
+               {
+                       if (GET_PREDICATELOCKTARGETTAG_RELATION(oldtarget->tag) 
!= indexId)
+                               continue;               /* not the index we're 
looking for */
+               }
+               else
+               {
+                       if (GET_PREDICATELOCKTARGETTAG_RELATION(oldtarget->tag) 
== heapId)
+                       {
+                               if 
(GET_PREDICATELOCKTARGETTAG_TYPE(oldtarget->tag) == PREDLOCKTAG_RELATION)
+                                       continue;       /* already the right 
lock */
+                       }
+                       else
+                       {
+                               /* This is an index.  Is it for the right heap? 
*/
+                               if 
(IndexGetRelation(GET_PREDICATELOCKTARGETTAG_RELATION(oldtarget->tag))
+                                       != heapId)
+                                       continue;       /* index on wrong heap 
relation */
+                       }
+               }
+ 
+               /*
+                * If we made it here, we have work to do.      We make sure 
the heap
+                * relation lock exists, then we walk the list of predicate 
locks for
+                * the old target we found, moving all locks to the heap 
relation lock
+                * -- unless they already hold that.
+                */
+ 
+               /*
+                * First make sure we have the heap relation target.  We only 
need to
+                * do this once.
+                */
+               if (heaptarget == NULL)
+               {
+                       heaptarget = 
hash_search_with_hash_value(PredicateLockTargetHash,
+                                                                               
                         &heaptargettag,
+                                                                               
                         heaptargettaghash,
+                                                                               
                         HASH_ENTER, &found);
+                       Assert(heaptarget != NULL);
+                       if (!found)
+                               SHMQueueInit(&heaptarget->predicateLocks);
+                       newpredlocktag.myTarget = heaptarget;
+               }
+ 
+               /*
+                * Loop through moving locks from this target to the relation 
target.
+                */
+               oldpredlock = (PREDICATELOCK *)
+                       SHMQueueNext(&(oldtarget->predicateLocks),
+                                                &(oldtarget->predicateLocks),
+                                                offsetof(PREDICATELOCK, 
targetLink));
+               while (oldpredlock)
+               {
+                       PREDICATELOCK *nextpredlock;
+                       PREDICATELOCK *newpredlock;
+                       SerCommitSeqNo oldCommitSeqNo = 
oldpredlock->commitSeqNo;
+ 
+                       nextpredlock = (PREDICATELOCK *)
+                               SHMQueueNext(&(oldtarget->predicateLocks),
+                                                        
&(oldpredlock->targetLink),
+                                                        
offsetof(PREDICATELOCK, targetLink));
+                       newpredlocktag.myXact = oldpredlock->tag.myXact;
+ 
+                       SHMQueueDelete(&(oldpredlock->xactLink));
+                       /* No need for retail delete from oldtarget list. */
+                       hash_search(PredicateLockHash,
+                                               &oldpredlock->tag,
+                                               HASH_REMOVE, &found);
+                       Assert(found);
+ 
+                       newpredlock = (PREDICATELOCK *)
+                               hash_search_with_hash_value
+                               (PredicateLockHash,
+                                &newpredlocktag,
+                                
PredicateLockHashCodeFromTargetHashCode(&newpredlocktag,
+                                                                               
                                 heaptargettaghash),
+                                HASH_ENTER_NULL, &found);
+                       Assert(newpredlock != NULL);
+                       if (!found)
+                       {
+                               
SHMQueueInsertBefore(&(heaptarget->predicateLocks),
+                                                                        
&(newpredlock->targetLink));
+                               
SHMQueueInsertBefore(&(newpredlocktag.myXact->predicateLocks),
+                                                                        
&(newpredlock->xactLink));
+                               newpredlock->commitSeqNo = oldCommitSeqNo;
+                       }
+                       else
+                       {
+                               if (newpredlock->commitSeqNo < oldCommitSeqNo)
+                                       newpredlock->commitSeqNo = 
oldCommitSeqNo;
+                       }
+ 
+                       Assert(newpredlock->commitSeqNo != 0);
+                       Assert((newpredlock->commitSeqNo == 
InvalidSerCommitSeqNo)
+                                  || (newpredlock->tag.myXact == 
OldCommittedSxact));
+ 
+                       oldpredlock = nextpredlock;
+               }
+ 
+               hash_search(PredicateLockTargetHash, &oldtarget->tag, 
HASH_REMOVE, &found);
+               Assert(found);
+       }
+ 
+       /* Put the reserved entry back */
+       hash_search_with_hash_value(PredicateLockTargetHash,
+                                                               
&ReservedTargetTag,
+                                                               
reservedtargettaghash,
+                                                               HASH_ENTER, 
&found);
+       Assert(!found);
+ 
+       /* Release locks in reverse order */
+       LWLockRelease(SerializableXactHashLock);
+       for (i = NUM_PREDICATELOCK_PARTITIONS - 1; i >= 0; i--)
+               LWLockRelease(FirstPredicateLockMgrLock + i);
+       LWLockRelease(SerializablePredicateLockListLock);
+ }
+ 
  
  /*
   *            PredicateLockPageSplit
***************
*** 2646,2655 **** PredicateLockPageSplit(const Relation relation, const 
BlockNumber oldblkno,
        bool            success;
  
        /*
!        * Bail out quickly if there are no serializable transactions
!        * running. As with PredicateLockTupleRowVersionLink, it's safe to
!        * check this without taking locks because the caller is holding
!        * the buffer page lock.
         */
        if (!TransactionIdIsValid(PredXact->SxactGlobalXmin))
                return;
--- 2858,2866 ----
        bool            success;
  
        /*
!        * Bail out quickly if there are no serializable transactions running. 
As
!        * with PredicateLockTupleRowVersionLink, it's safe to check this 
without
!        * taking locks because the caller is holding the buffer page lock.
         */
        if (!TransactionIdIsValid(PredXact->SxactGlobalXmin))
                return;
*** a/src/include/catalog/index.h
--- b/src/include/catalog/index.h
***************
*** 66,71 **** extern void index_drop(Oid indexId);
--- 66,73 ----
  
  extern IndexInfo *BuildIndexInfo(Relation index);
  
+ extern Oid IndexGetRelation(Oid indexId);
+ 
  extern void FormIndexDatum(IndexInfo *indexInfo,
                           TupleTableSlot *slot,
                           EState *estate,
*** a/src/include/storage/predicate.h
--- b/src/include/storage/predicate.h
***************
*** 50,55 **** extern void PredicateLockTuple(const Relation relation, const 
HeapTuple tuple);
--- 50,56 ----
  extern void PredicateLockTupleRowVersionLink(const Relation relation, const 
HeapTuple oldTuple, const HeapTuple newTuple);
  extern void PredicateLockPageSplit(const Relation relation, const BlockNumber 
oldblkno, const BlockNumber newblkno);
  extern void PredicateLockPageCombine(const Relation relation, const 
BlockNumber oldblkno, const BlockNumber newblkno);
+ extern void TransferPredicateLocksToHeapRelation(const Relation relation);
  extern void ReleasePredicateLocks(const bool isCommit);
  
  /* conflict detection (may also trigger rollback) */
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to