]> granicus.if.org Git - postgresql/commitdiff
Fix possibile deadlock when dropping partitions.
authorRobert Haas <rhaas@postgresql.org>
Tue, 11 Apr 2017 13:08:36 +0000 (09:08 -0400)
committerRobert Haas <rhaas@postgresql.org>
Tue, 11 Apr 2017 13:08:36 +0000 (09:08 -0400)
heap_drop_with_catalog and RangeVarCallbackForDropRelation should
lock the parent before locking the target relation.

Amit Langote

Discussion: http://postgr.es/m/29588799-a8ce-b0a2-3dae-f39ff6d35922@lab.ntt.co.jp

src/backend/catalog/heap.c
src/backend/commands/tablecmds.c

index a264f1b9eb99d0e6e4a176de2274e12bed89e7d3..4a5f545dc67228159de76fdb11f5638478b68422 100644 (file)
@@ -1759,29 +1759,33 @@ void
 heap_drop_with_catalog(Oid relid)
 {
        Relation        rel;
+       HeapTuple       tuple;
        Oid                     parentOid;
        Relation        parent = NULL;
 
        /*
-        * Open and lock the relation.
+        * To drop a partition safely, we must grab exclusive lock on its parent,
+        * because another backend might be about to execute a query on the parent
+        * table.  If it relies on previously cached partition descriptor, then
+        * it could attempt to access the just-dropped relation as its partition.
+        * We must therefore take a table lock strong enough to prevent all
+        * queries on the table from proceeding until we commit and send out a
+        * shared-cache-inval notice that will make them update their index lists.
         */
-       rel = relation_open(relid, AccessExclusiveLock);
-
-       /*
-        * If the relation is a partition, we must grab exclusive lock on its
-        * parent because we need to update its partition descriptor. We must take
-        * a table lock strong enough to prevent all queries on the parent from
-        * proceeding until we commit and send out a shared-cache-inval notice
-        * that will make them update their partition descriptor. Sometimes, doing
-        * this is cycles spent uselessly, especially if the parent will be
-        * dropped as part of the same command anyway.
-        */
-       if (rel->rd_rel->relispartition)
+       tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
+       if (((Form_pg_class) GETSTRUCT(tuple))->relispartition)
        {
                parentOid = get_partition_parent(relid);
                parent = heap_open(parentOid, AccessExclusiveLock);
        }
 
+       ReleaseSysCache(tuple);
+
+       /*
+        * Open and lock the relation.
+        */
+       rel = relation_open(relid, AccessExclusiveLock);
+
        /*
         * There can no longer be anyone *else* touching the relation, but we
         * might still have open queries or cursors, or pending trigger events, in
index abb262b851cc90bfb75c80b887c47bfe9b4f91e6..a6a9f54b13edd9ec0c8ece93f6e117c27c9337a4 100644 (file)
@@ -271,6 +271,7 @@ struct DropRelationCallbackState
 {
        char            relkind;
        Oid                     heapOid;
+       Oid                     partParentOid;
        bool            concurrent;
 };
 
@@ -1049,6 +1050,7 @@ RemoveRelations(DropStmt *drop)
                /* Look up the appropriate relation using namespace search. */
                state.relkind = relkind;
                state.heapOid = InvalidOid;
+               state.partParentOid = InvalidOid;
                state.concurrent = drop->concurrent;
                relOid = RangeVarGetRelidExtended(rel, lockmode, true,
                                                                                  false,
@@ -1078,6 +1080,8 @@ RemoveRelations(DropStmt *drop)
 /*
  * Before acquiring a table lock, check whether we have sufficient rights.
  * In the case of DROP INDEX, also try to lock the table before the index.
+ * Also, if the table to be dropped is a partition, we try to lock the parent
+ * first.
  */
 static void
 RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
@@ -1087,6 +1091,7 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
        struct DropRelationCallbackState *state;
        char            relkind;
        char            expected_relkind;
+       bool            is_partition;
        Form_pg_class classform;
        LOCKMODE        heap_lockmode;
 
@@ -1106,6 +1111,17 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
                state->heapOid = InvalidOid;
        }
 
+       /*
+        * Similarly, if we previously locked some other partition's heap, and
+        * the name we're looking up no longer refers to that relation, release
+        * the now-useless lock.
+        */
+       if (relOid != oldRelOid && OidIsValid(state->partParentOid))
+       {
+               UnlockRelationOid(state->partParentOid, AccessExclusiveLock);
+               state->partParentOid = InvalidOid;
+       }
+
        /* Didn't find a relation, so no need for locking or permission checks. */
        if (!OidIsValid(relOid))
                return;
@@ -1114,6 +1130,7 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
        if (!HeapTupleIsValid(tuple))
                return;                                 /* concurrently dropped, so nothing to do */
        classform = (Form_pg_class) GETSTRUCT(tuple);
+       is_partition = classform->relispartition;
 
        /*
         * Both RELKIND_RELATION and RELKIND_PARTITIONED_TABLE are OBJECT_TABLE,
@@ -1157,6 +1174,19 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
                if (OidIsValid(state->heapOid))
                        LockRelationOid(state->heapOid, heap_lockmode);
        }
+
+       /*
+        * Similarly, if the relation is a partition, we must acquire lock on its
+        * parent before locking the partition.  That's because queries lock the
+        * parent before its partitions, so we risk deadlock it we do it the other
+        * way around.
+        */
+       if (is_partition && relOid != oldRelOid)
+       {
+               state->partParentOid = get_partition_parent(relOid);
+               if (OidIsValid(state->partParentOid))
+                       LockRelationOid(state->partParentOid, AccessExclusiveLock);
+       }
 }
 
 /*