v2-0002-Fix-crash-of-self-referencing-tables-with-delete-tri.patch
text/x-patch
Filename: v2-0002-Fix-crash-of-self-referencing-tables-with-delete-tri.patch
Type: text/x-patch
Part: 0
From 4949efbd17696ceffcd681555a4875b29a1d43ab Mon Sep 17 00:00:00 2001
From: luquijeffrey <lucas.jeffrey@anachronics.com>
Date: Fri, 29 May 2026 12:23:54 -0300
Subject: [PATCH 2/2] =?UTF-8?q?Fix=20crash=20of=20self=E2=80=91referencing?=
=?UTF-8?q?=20tables=20with=20delete=20triggers?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/backend/utils/adt/ri_triggers.c | 86 ++++++++++++++++++++++++++++-
1 file changed, 85 insertions(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index dc89c686394..10019dc8ec5 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -251,12 +251,24 @@ typedef struct RI_FastPathEntry
int batch_count;
} RI_FastPathEntry;
+/*
+ * RI_QueryPlanCacheExecutingRefCountEntry
+ *
+ * Entry to track the number of times a prepared plan is being executed.
+ */
+typedef struct RI_QueryPlanCacheExecutingRefCountEntry
+{
+ SPIPlanPtr plan;
+ uint32 refcount; /* number of times this plan is being executed (can be more than 1 if reentrant) */
+} RI_QueryPlanCacheExecutingRefCountEntry;
+
/*
* Local data
*/
static HTAB *ri_constraint_cache = NULL;
static HTAB *ri_query_cache = NULL;
static HTAB *ri_compare_cache = NULL;
+static HTAB *ri_query_plan_cache_executing_refcount = NULL;
static dclist_head ri_constraint_cache_valid_list;
static HTAB *ri_fastpath_cache = NULL;
@@ -295,6 +307,11 @@ static SPIPlanPtr ri_FetchPreparedPlan(RI_QueryKey *key);
static void ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan);
static RI_CompareHashEntry *ri_HashCompareOp(Oid eq_opr, Oid typeid);
+/* Reentrancy protection: prevent segfault on deleting a plan in execution if invalidated during reentrant RI check. */
+static void ri_PreparedPlanExecutionStarted(SPIPlanPtr plan);
+static void ri_PreparedPlanExecutionFinished(SPIPlanPtr plan);
+static bool ri_PreparedPlanCanRelease(SPIPlanPtr plan);
+
static void ri_CheckTrigger(FunctionCallInfo fcinfo, const char *funcname,
int tgkind);
static RI_ConstraintInfo *ri_FetchConstraintInfo(Trigger *trigger,
@@ -2724,6 +2741,9 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
save_sec_context | SECURITY_LOCAL_USERID_CHANGE |
SECURITY_NOFORCE_RLS);
+ /* Increase plan use count for reentrancy protection. */
+ ri_PreparedPlanExecutionStarted(qplan);
+
/*
* Finally we can run the query.
*
@@ -2735,6 +2755,9 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo,
vals, nulls,
test_snapshot, crosscheck_snapshot,
false, false, limit);
+
+ /* Decrease plan use count. this call can free the plan if it was invalidated and no longer in use. */
+ ri_PreparedPlanExecutionFinished(qplan);
/* Restore UID and security context */
SetUserIdAndSecContext(save_userid, save_sec_context);
@@ -3762,6 +3785,12 @@ ri_InitHashTables(void)
ri_compare_cache = hash_create("RI compare cache",
RI_INIT_QUERYHASHSIZE,
&ctl, HASH_ELEM | HASH_BLOBS);
+
+ ctl.keysize = sizeof(SPIPlanPtr);
+ ctl.entrysize = sizeof(RI_QueryPlanCacheExecutingRefCountEntry);
+ ri_query_plan_cache_executing_refcount = hash_create("RI plan cache execution refcount",
+ RI_INIT_QUERYHASHSIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
}
@@ -3811,7 +3840,7 @@ ri_FetchPreparedPlan(RI_QueryKey *key)
* memory space before we make a new one.
*/
entry->plan = NULL;
- if (plan)
+ if (plan && ri_PreparedPlanCanRelease(plan))
SPI_freeplan(plan);
return NULL;
@@ -3847,6 +3876,61 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
}
+static void
+ri_PreparedPlanExecutionStarted(SPIPlanPtr plan)
+{
+ RI_QueryPlanCacheExecutingRefCountEntry* entry;
+ bool found;
+
+ if (!ri_query_plan_cache_executing_refcount)
+ ri_InitHashTables();
+
+ entry = (RI_QueryPlanCacheExecutingRefCountEntry*) hash_search(ri_query_plan_cache_executing_refcount, &plan, HASH_ENTER, &found);
+ if (found)
+ entry->refcount++;
+ else
+ entry->refcount = 1;
+}
+
+static void
+ri_PreparedPlanExecutionFinished(SPIPlanPtr plan)
+{
+ RI_QueryPlanCacheExecutingRefCountEntry* entry;
+ bool found;
+
+ if (!ri_query_plan_cache_executing_refcount)
+ return;
+
+ entry = (RI_QueryPlanCacheExecutingRefCountEntry*) hash_search(ri_query_plan_cache_executing_refcount, &plan, HASH_FIND, &found);
+ if (!entry)
+ return;
+
+ entry->refcount--;
+ if (entry->refcount == 0 && !SPI_plan_is_valid(plan))
+ {
+ // Remove the entry
+ hash_search(ri_query_plan_cache_executing_refcount, &plan, HASH_REMOVE, NULL);
+ SPI_freeplan(plan);
+ }
+}
+
+static bool
+ri_PreparedPlanCanRelease(SPIPlanPtr plan)
+{
+ RI_QueryPlanCacheExecutingRefCountEntry* entry;
+ bool found;
+
+ if (!ri_query_plan_cache_executing_refcount)
+ return true;
+
+ entry = (RI_QueryPlanCacheExecutingRefCountEntry*) hash_search(ri_query_plan_cache_executing_refcount, &plan, HASH_FIND, &found);
+ if (!entry)
+ return true;
+
+ return entry->refcount == 0;
+}
+
+
/*
* ri_KeysEqual -
*
--
2.34.1