From c5c14c328087ab7d52d6e3430744580be37b6dc8 Mon Sep 17 00:00:00 2001 From: David Rowley Date: Mon, 24 Aug 2026 16:53:15 +1200 Subject: [PATCH v16] Close relations opened specifically for AFTER triggers 39dcfda2d fixed an incorrect reuse of ResultRelInfos for AFTER triggers when the ResultRelInfo needed to have a different ri_RootResultRelInfo. That caused an issue in logical replication apply workers as finish_edata() neglects to call ExecCloseResultRelations() and instead relies on ExecCleanupTupleRouting() to close relations opened during partitioning's tuple routing. Since 39dcfda2d, because we may have done some additional table_opens() calls due to having to create an additional ResultRelInfo because of requirements to have a different ri_RootResultRelInfo, we should now be explicitly closing any relations opened on ResultRelInfos in EState's es_trig_target_relations. Author: Hayato Kuroda (Fujitsu) Author: David Rowley Discussion: https://postgr.es/m/OS9PR01MB121491E7E05950D108AF9A6D8F5A72@OS9PR01MB12149.jpnprd01.prod.outlook.com Backpatch-through: 15 --- src/backend/executor/execMain.c | 16 ++++++++++++++++ src/backend/replication/logical/worker.c | 12 ++++++++---- src/include/executor/executor.h | 1 + src/test/subscription/t/013_partition.pl | 7 +++++++ 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 6a60fb33377..c3ca801f2a8 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -1621,6 +1621,22 @@ ExecCloseResultRelations(EState *estate) } } + /* + * Now close any relations that we opened for trigger target + * ResultRelInfos. + */ + ExecCloseTrigTargetRelations(estate); +} + +/* + * Close any relations that have been opened for ResultRelInfos opened + * specifically for trigger target relations. + */ +void +ExecCloseTrigTargetRelations(EState *estate) +{ + ListCell *l; + /* Close any relations that have been opened by ExecGetTriggerResultRel(). */ foreach(l, estate->es_trig_target_relations) { diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index c3dd1537185..76367458768 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -731,12 +731,16 @@ finish_edata(ApplyExecutionData *edata) ExecCleanupTupleRouting(edata->mtstate, edata->proute); /* - * Cleanup. It might seem that we should call ExecCloseResultRelations() - * here, but we intentionally don't. It would close the rel we added to + * Close relations opened specifically for trigger targets. It might seem + * that we should call ExecCloseResultRelations() here, but we + * intentionally don't as that would close the rel we added to * es_opened_result_relations above, which is wrong because we took no - * corresponding refcount. We rely on ExecCleanupTupleRouting() to close - * any other relations opened during execution. + * corresponding refcount. ExecCleanupTupleRouting() closes relations + * opened for tuple routing, while ExecCloseTrigTargetRelations() closes + * any relations we opened for AFTER triggers. */ + ExecCloseTrigTargetRelations(estate); + ExecResetTupleTable(estate->es_tupleTable, false); FreeExecutorState(estate); pfree(edata); diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index baef7e031ee..deba7a64af0 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -586,6 +586,7 @@ extern Relation ExecOpenScanRelation(EState *estate, Index scanrelid, int eflags extern void ExecInitRangeTable(EState *estate, List *rangeTable, List *permInfos); extern void ExecCloseRangeTableRelations(EState *estate); extern void ExecCloseResultRelations(EState *estate); +extern void ExecCloseTrigTargetRelations(EState *estate); static inline RangeTblEntry * exec_rt_fetch(Index rti, EState *estate) diff --git a/src/test/subscription/t/013_partition.pl b/src/test/subscription/t/013_partition.pl index 275fb3b5257..912ca3336bd 100644 --- a/src/test/subscription/t/013_partition.pl +++ b/src/test/subscription/t/013_partition.pl @@ -886,4 +886,11 @@ $result = $node_subscriber2->safe_psql('postgres', "SELECT a, b, c FROM tab5_1 ORDER BY 1"); is($result, qq(4||1), 'updates of tab5 replicated correctly'); +# Validate we didn't neglect to cleanup any resources on either subscriber. +foreach my $node ($node_subscriber1, $node_subscriber2) +{ + ok(!$node->log_contains(qr/relcache reference leak/), + 'no unclosed resources on ' . $node->name); +} + done_testing(); -- 2.53.0