Hi, IO worker processes call smgropen with smgr_aio_reopen() -> smgropen() path. smgropen() has a comment mentioning:
/* * If this function is called * outside of a transaction, the object remains valid until smgrdestroy() or * smgrdestroyall() is called. /* Since, IO workers call this function outside of a transaction, smgrdestroy() needs to be called. Attached patch calls smgrdestroy() when the IO worker is idle. There is a chance that IO workers won't be idle forever, so we might want to trigger this after #n number of IOs too but I didn't implement like that for the sake of simplicity. -- Regards, Nazir Bilal Yavuz Microsoft
From 8bbfb6bde682ef7ce1439e318e378a196b8e4dfc Mon Sep 17 00:00:00 2001 From: Nazir Bilal Yavuz <[email protected]> Date: Fri, 18 Sep 2026 10:17:23 +0300 Subject: [PATCH v1] aio: worker: Free SMGR objects when idle IO workers create SMGR objects when reopening relations, but don't have a transaction-end cleanup to destroy them. Long-lived workers can therefore retain entries for an increasing number of relations, including dropped ones. Destroy these objects when when a completed checkpoint is observed and when the worker is idle. At this point no borrowed descriptors or SMGR references remain in use. --- src/backend/storage/aio/method_worker.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/backend/storage/aio/method_worker.c b/src/backend/storage/aio/method_worker.c index cf75b2816b7..7639d093677 100644 --- a/src/backend/storage/aio/method_worker.c +++ b/src/backend/storage/aio/method_worker.c @@ -34,6 +34,7 @@ #include "miscadmin.h" #include "port/pg_bitutils.h" #include "postmaster/auxprocess.h" +#include "postmaster/bgwriter.h" #include "postmaster/interrupt.h" #include "storage/aio.h" #include "storage/aio_internal.h" @@ -45,6 +46,7 @@ #include "storage/pmsignal.h" #include "storage/proc.h" #include "storage/shmem.h" +#include "storage/smgr.h" #include "tcop/tcopprot.h" #include "utils/injection_point.h" #include "utils/memdebug.h" @@ -962,6 +964,16 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len) /* Cancel new worker request if pending. */ pgaio_worker_cancel_grow(); + /* + * Like the background writer, IO workers don't have a + * transaction-end cleanup to destroy SMGR objects, so do that + * when idle after a checkpoint. Any IO has completed and its + * error context has been cleared here, so no borrowed file + * descriptors or SMGR references remain in use. + */ + if (FirstCallSinceLastCheckpoint()) + smgrdestroyall(); + /* Compute the remaining allowed idle time. */ if (io_worker_idle_timeout == -1) { -- 2.47.3
