From df4a55f7ba7a67730f2847aa050f374d18a9a5ec Mon Sep 17 00:00:00 2001 From: David Rowley Date: Mon, 24 Aug 2026 16:53:15 +1200 Subject: [PATCH v20] 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. Reported-by: Hayato Kuroda (Fujitsu) 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 fde502efd38..6e47856cf25 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -1670,6 +1670,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 92ea1d0df24..3ed169e1eaf 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -945,12 +945,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 1798e6027d4..190e8a4897a 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -703,6 +703,7 @@ extern void ExecInitRangeTable(EState *estate, List *rangeTable, List *permInfos Bitmapset *unpruned_relids); 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 234d4f003b7..a6ada211fab 100644 --- a/src/test/subscription/t/013_partition.pl +++ b/src/test/subscription/t/013_partition.pl @@ -897,4 +897,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/resource was not closed/), + 'unclosed resources on ' . $node->name); +} + done_testing(); -- 2.53.0