From 86188a643aebffc39a03b233362d4cd74506fd53 Mon Sep 17 00:00:00 2001
From: Alexandre Felipe <o.alexandre.felipe@gmail.com>
Date: Thu, 17 Sep 2026 21:30:53 +0100
Subject: [PATCH] pgaio: file descriptor cache invalidation.

It was reported that io_method=worker, I/O worker processes
retain open file descriptors on relation that have been dropped [1]

Regular backends do not accumulate them: they receive SMGR invalidation
via the shared invalidation queue I/O workers were not handling invalidation
messages.

This patch implements cache invalidation processing for I/O workers.

1. https://www.postgresql.org/message-id/19622-639a4ba94c5a53d7%40postgresql.org
---
 src/backend/storage/aio/method_worker.c | 32 +++++++++++++++++
 src/backend/storage/smgr/smgr.c         | 46 +++++++++++++++++++------
 src/include/storage/smgr.h              |  1 +
 3 files changed, 69 insertions(+), 10 deletions(-)

diff --git a/src/backend/storage/aio/method_worker.c b/src/backend/storage/aio/method_worker.c
index cf75b2816b7..fc9da145b8f 100644
--- a/src/backend/storage/aio/method_worker.c
+++ b/src/backend/storage/aio/method_worker.c
@@ -44,7 +44,9 @@
 #include "storage/lwlock.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
+#include "storage/sinvaladt.h"
 #include "storage/shmem.h"
+#include "storage/smgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/injection_point.h"
 #include "utils/memdebug.h"
@@ -683,6 +685,25 @@ check_io_worker_gucs(void)
 						   "io_max_workers", io_max_workers)));
 }
 
+/*
+ * Handle cache invalidation messages for IoWorker
+ *
+ * Same as LocalExecuteInvalidationMessage for backends,
+ * but in IoWorkers handle only file invalidation messages.
+ */
+static void
+pgaio_cache_invalidation_callback(SharedInvalidationMessage *msg)
+{
+	if (msg->id == SHAREDINVALSMGR_ID)
+	{
+		RelFileLocatorBackend rlocator;
+
+		rlocator.locator = msg->sm.rlocator;
+		rlocator.backend = (msg->sm.backend_hi << 16) | (int) msg->sm.backend_lo;
+		smgrdestroyrellocator(rlocator);
+	}
+}
+
 void
 IoWorkerMain(const void *startup_data, size_t startup_data_len)
 {
@@ -698,6 +719,11 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	AuxiliaryProcessMainCommon();
 
+	/*
+	 * IO workers cache file descriptors locally, subscribe to cluster-wide
+	 * cache invalidation events.
+	 */
+	SharedInvalBackendInit(false);
 	pqsignal(SIGHUP, SignalHandlerForConfigReload);
 	pqsignal(SIGINT, die);		/* to allow manually triggering worker restart */
 
@@ -1032,6 +1058,12 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 
 		CHECK_FOR_INTERRUPTS();
 
+		/*
+		 * Handle pending invalidation one by one. smgrdestroyall used when
+		 * there are too many pending invalidations.
+		 */
+		ReceiveSharedInvalidMessages(pgaio_cache_invalidation_callback, smgrdestroyall);
+
 		if (ConfigReloadPending)
 		{
 			int			io_max_workers_prev = io_max_workers;
diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c
index 5391640d861..8cf592281bc 100644
--- a/src/backend/storage/smgr/smgr.c
+++ b/src/backend/storage/smgr/smgr.c
@@ -164,7 +164,6 @@ static dlist_head unpinned_relns;
 /* local function prototypes */
 static void smgrshutdown(int code, Datum arg);
 static void smgrdestroy(SMgrRelation reln);
-
 static void smgr_aio_reopen(PgAioHandle *ioh);
 static char *smgr_aio_describe_identity(const PgAioTargetData *sd);
 
@@ -317,7 +316,10 @@ smgrunpin(SMgrRelation reln)
 }
 
 /*
- * smgrdestroy() -- Delete an SMgrRelation object.
+ * smgrdestroy() -- Destroy an SMgrRelation object.
+ *
+ * This closes associated files, and frees memory allocated for a relation
+ * by removing any in-memory reference.
  */
 static void
 smgrdestroy(SMgrRelation reln)
@@ -342,9 +344,10 @@ smgrdestroy(SMgrRelation reln)
 }
 
 /*
- * smgrrelease() -- Release all resources used by this object.
+ * smgrrelease() -- Close relation files.
  *
- * The object remains valid.
+ * In contrast to smgrdestroy(), smgrrelease() retains the relation entry,
+ * and will not free memory associated with it.
  */
 void
 smgrrelease(SMgrRelation reln)
@@ -377,7 +380,7 @@ smgrclose(SMgrRelation reln)
 }
 
 /*
- * smgrdestroyall() -- Release resources used by all unpinned objects.
+ * smgrdestroyall() -- Destroy all unpinned objects.
  *
  * It must be known that there are no pointers to SMgrRelations, other than
  * those pinned with smgrpin().
@@ -406,7 +409,7 @@ smgrdestroyall(void)
 }
 
 /*
- * smgrreleaseall() -- Release resources used by all objects.
+ * smgrreleaseall() -- Close all relations.
  */
 void
 smgrreleaseall(void)
@@ -432,12 +435,11 @@ smgrreleaseall(void)
 }
 
 /*
- * smgrreleaserellocator() -- Release resources for given RelFileLocator, if
- *							  it's open.
+ * smgrreleaserellocator() -- Locate a relation and close it.
  *
  * This has the same effects as smgrrelease(smgropen(rlocator)), but avoids
- * uselessly creating a hashtable entry only to drop it again when no
- * such entry exists already.
+ * uselessly creating a hashtable entry, if a hashtable entry exists it is
+ * retained.
  */
 void
 smgrreleaserellocator(RelFileLocatorBackend rlocator)
@@ -455,6 +457,30 @@ smgrreleaserellocator(RelFileLocatorBackend rlocator)
 		smgrrelease(reln);
 }
 
+/*
+ * smgrdestroyrellocator() -- Locate a relation and destroy it.
+ *
+ * This has the same effects as smgrdestroy(smgropen(rlocator)), but avoids
+ * uselessly creating a hashtable entry only to drop it again when no
+ * such entry exists already.
+ */
+void
+smgrdestroyrellocator(RelFileLocatorBackend rlocator)
+{
+	SMgrRelation reln;
+
+	/* Nothing to do if hashtable not set up */
+	if (SMgrRelationHash == NULL)
+		return;
+
+	reln = (SMgrRelation) hash_search(SMgrRelationHash,
+									  &rlocator,
+									  HASH_FIND, NULL);
+	if (reln != NULL)
+		smgrdestroy(reln);
+}
+
+
 /*
  * smgrexists() -- Does the underlying file for a fork exist?
  */
diff --git a/src/include/storage/smgr.h b/src/include/storage/smgr.h
index 09bd42fcf4b..417cbaaba6d 100644
--- a/src/include/storage/smgr.h
+++ b/src/include/storage/smgr.h
@@ -86,6 +86,7 @@ extern void smgrdestroyall(void);
 extern void smgrrelease(SMgrRelation reln);
 extern void smgrreleaseall(void);
 extern void smgrreleaserellocator(RelFileLocatorBackend rlocator);
+extern void smgrdestroyrellocator(RelFileLocatorBackend rlocator);
 extern void smgrcreate(SMgrRelation reln, ForkNumber forknum, bool isRedo);
 extern void smgrdosyncall(SMgrRelation *rels, int nrels);
 extern void smgrdounlinkall(SMgrRelation *rels, int nrels, bool isRedo);
-- 
2.53.0

