From 5ea789323d2005b6df8e0adc88151823e1fbed28 Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Tue, 31 Mar 2026 20:53:43 +0900 Subject: [PATCH v13 1/2] Fix two issues in fast-path FK check introduced by commit 2da86c1ef9 First, under CLOBBER_CACHE_ALWAYS, the RI_ConstraintInfo entry can be invalidated by relcache callbacks triggered inside table_open() or index_open(), leaving ri_FastPathCheck() calling ri_populate_fastpath_metadata() with a stale entry whose valid flag is false. Fix by reloading riinfo after the relation opens and populating fpmeta immediately, then calling ri_ExtractValues() and build_index_scankeys() before any further operations that could trigger invalidation. Second, fpmeta was not freed or cleared when the entry was invalidated in InvalidateConstraintCacheCallBack(), leaving a dangling pointer that caused a crash on the next invocation when the guard `if (riinfo->fpmeta == NULL)` incorrectly skipped repopulation. Fix by freeing and NULLing fpmeta at invalidation time. Noticed locally when testing with CLOBBER_CACHE_ALWAYS. --- src/backend/utils/adt/ri_triggers.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index ffaa0e749cb..d57343f85d8 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -2486,6 +2486,16 @@ InvalidateConstraintCacheCallBack(Datum arg, SysCacheIdentifier cacheid, riinfo->rootHashValue == hashvalue) { riinfo->valid = false; + /* + * Free and clear any cached fast-path metadata so the next use + * repopulates it from scratch rather than following a dangling + * pointer. + */ + if (riinfo->fpmeta) + { + pfree(riinfo->fpmeta); + riinfo->fpmeta = NULL; + } /* Remove invalidated entries from the list, too */ dclist_delete_from(&ri_constraint_cache_valid_list, iter.cur); } @@ -2714,17 +2724,23 @@ ri_FastPathCheck(const RI_ConstraintInfo *riinfo, pk_rel = table_open(riinfo->pk_relid, RowShareLock); idx_rel = index_open(riinfo->conindid, AccessShareLock); + if (riinfo->fpmeta == NULL) + { + /* Reload to ensure it's valid. */ + riinfo = ri_LoadConstraintInfo(riinfo->constraint_id); + ri_populate_fastpath_metadata((RI_ConstraintInfo *) riinfo, + fk_rel, idx_rel); + } + Assert(riinfo->fpmeta); + ri_ExtractValues(fk_rel, newslot, riinfo, false, pk_vals, pk_nulls); + build_index_scankeys(riinfo, idx_rel, pk_vals, pk_nulls, skey); + slot = table_slot_create(pk_rel, NULL); scandesc = index_beginscan(pk_rel, idx_rel, snapshot, NULL, riinfo->nkeys, 0, SO_NONE); - if (riinfo->fpmeta == NULL) - ri_populate_fastpath_metadata((RI_ConstraintInfo *) riinfo, - fk_rel, idx_rel); - Assert(riinfo->fpmeta); - GetUserIdAndSecContext(&saved_userid, &saved_sec_context); SetUserIdAndSecContext(RelationGetForm(pk_rel)->relowner, saved_sec_context | @@ -2732,8 +2748,6 @@ ri_FastPathCheck(const RI_ConstraintInfo *riinfo, SECURITY_NOFORCE_RLS); ri_CheckPermissions(pk_rel); - ri_ExtractValues(fk_rel, newslot, riinfo, false, pk_vals, pk_nulls); - build_index_scankeys(riinfo, idx_rel, pk_vals, pk_nulls, skey); found = ri_FastPathProbeOne(pk_rel, idx_rel, scandesc, slot, snapshot, riinfo, skey, riinfo->nkeys); SetUserIdAndSecContext(saved_userid, saved_sec_context); -- 2.47.3