Fix table lock levels for REINDEX INDEX CONCURRENTLY
authorPeter Eisentraut <peter@eisentraut.org>
Wed, 8 May 2019 12:15:01 +0000 (14:15 +0200)
committerPeter Eisentraut <peter@eisentraut.org>
Wed, 8 May 2019 12:30:23 +0000 (14:30 +0200)
REINDEX CONCURRENTLY locks tables with ShareUpdateExclusiveLock rather
than the ShareLock used by a plain REINDEX.  However,
RangeVarCallbackForReindexIndex() was not updated for that and still
used the ShareLock only.  This would lead to lock upgrades later,
leading to possible deadlocks.

Reported-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Discussion: https://www.postgresql.org/message-id/flat/20190430151735.wi52sxjvxsjvaxxt%40alap3.anarazel.de

src/backend/commands/indexcmds.c

index 663407c65d33f1e66374c4978f3d0beb8308564a..1db10d2360623507c4c1d7fcc12b77d894eb41d0 100644 (file)
@@ -91,6 +91,15 @@ static bool ReindexRelationConcurrently(Oid relationOid, int options);
 static void ReindexPartitionedIndex(Relation parentIdx);
 static void update_relispartition(Oid relationId, bool newval);
 
+/*
+ * callback argument type for RangeVarCallbackForReindexIndex()
+ */
+struct ReindexIndexCallbackState
+{
+   bool        concurrent;         /* flag from statement */
+   Oid         locked_table_oid;   /* tracks previously locked table */
+};
+
 /*
  * CheckIndexCompatible
  *     Determine whether an existing index definition is compatible with a
@@ -2303,8 +2312,8 @@ ChooseIndexColumnNames(List *indexElems)
 void
 ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
 {
+   struct ReindexIndexCallbackState state;
    Oid         indOid;
-   Oid         heapOid = InvalidOid;
    Relation    irel;
    char        persistence;
 
@@ -2313,11 +2322,13 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent)
     * obtain lock on table first, to avoid deadlock hazard.  The lock level
     * used here must match the index lock obtained in reindex_index().
     */
+   state.concurrent = concurrent;
+   state.locked_table_oid = InvalidOid;
    indOid = RangeVarGetRelidExtended(indexRelation,
                                      concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock,
                                      0,
                                      RangeVarCallbackForReindexIndex,
-                                     (void *) &heapOid);
+                                     &state);
 
    /*
     * Obtain the current persistence of the existing index.  We already hold
@@ -2350,7 +2361,15 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
                                Oid relId, Oid oldRelId, void *arg)
 {
    char        relkind;
-   Oid        *heapOid = (Oid *) arg;
+   struct ReindexIndexCallbackState *state = arg;
+   LOCKMODE    table_lockmode;
+
+   /*
+    * Lock level here should match table lock in reindex_index() for
+    * non-concurrent case and table locks used by index_concurrently_*() for
+    * concurrent case.
+    */
+   table_lockmode = state->concurrent ? ShareUpdateExclusiveLock : ShareLock;
 
    /*
     * If we previously locked some other index's heap, and the name we're
@@ -2359,9 +2378,8 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
     */
    if (relId != oldRelId && OidIsValid(oldRelId))
    {
-       /* lock level here should match reindex_index() heap lock */
-       UnlockRelationOid(*heapOid, ShareLock);
-       *heapOid = InvalidOid;
+       UnlockRelationOid(state->locked_table_oid, table_lockmode);
+       state->locked_table_oid = InvalidOid;
    }
 
    /* If the relation does not exist, there's nothing more to do. */
@@ -2389,14 +2407,17 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
    /* Lock heap before index to avoid deadlock. */
    if (relId != oldRelId)
    {
+       Oid         table_oid = IndexGetRelation(relId, true);
+
        /*
-        * Lock level here should match reindex_index() heap lock. If the OID
-        * isn't valid, it means the index as concurrently dropped, which is
-        * not a problem for us; just return normally.
+        * If the OID isn't valid, it means the index was concurrently
+        * dropped, which is not a problem for us; just return normally.
         */
-       *heapOid = IndexGetRelation(relId, true);
-       if (OidIsValid(*heapOid))
-           LockRelationOid(*heapOid, ShareLock);
+       if (OidIsValid(table_oid))
+       {
+           LockRelationOid(table_oid, table_lockmode);
+           state->locked_table_oid = table_oid;
+       }
    }
 }