Hello Hackers,

I noticed a file descriptor leak when running PostgreSQL 18 with
io_method=io_uring. As far as I could tell, it only triggers when
the server restarts due to one of the backends being killed.

How to reproduce the issue:
- Setup a server configured with io_uring
- Check the number of open file descriptors:
    ls -la "/proc/$(head -1 $PGDATA/postmaster.pid)/fd/" | grep "io_uring" | wc 
-l
- SIGKILL a backend to trigger a server restart:
    kill -9 $(psql -XtA -U postgres -c "SELECT pid FROM pg_stat_activity WHERE 
backend_type = 'client backend' LIMIT 1")
- Check the number of open files descriptors again:
    Expected: FD count remains the same.
    Actual: FD count has doubled.

Tested on PostgreSQL 18.0, 18.1, 18.2, 18.3 and git master on the
following platforms:
- Ubuntu Server 24.04: Linux 6.8.0, liburing 2.5
- Exherbo Linux: Linux 6.16.12, liburing 2.12
- Fedora Linux: Linux 6.19.7, liburing 2.13-dev

>From what I could gather, this happens because 
pgaio_uring_shmem_init() in 
src/backend/storage/aio/method_io_uring.c doesn't cleanup 
the allocated resources on exit.

I've attached a patch which registers an on_shmem_exit() callback 
to close the file descriptors on server exit. I took inspiration
from how src/backend/storage/aio/method_worker.c handles cleanup.

Regards,

Lucas.
From 9728c2c28420677719e45139d9cbf16526cb1656 Mon Sep 17 00:00:00 2001
From: Lucas DRAESCHER <[email protected]>
Date: Tue, 17 Mar 2026 17:26:11 +0100
Subject: [PATCH v1] Release io_uring resources on shmem exit

io_uring_queue_init() allocates resources for each io_uring instance, but
pgaio_uring_shmem_init() never registered a cleanup callback to free them.

Add an on_shmem_exit() callback that calls io_uring_queue_exit().

The callback and its registration follow the same pattern as
pgaio_worker_die() in method_worker.c.
---
 src/backend/storage/aio/method_io_uring.c | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/src/backend/storage/aio/method_io_uring.c b/src/backend/storage/aio/method_io_uring.c
index 4867ded35ea..61984b798a9 100644
--- a/src/backend/storage/aio/method_io_uring.c
+++ b/src/backend/storage/aio/method_io_uring.c
@@ -37,6 +37,7 @@
 #include "miscadmin.h"
 #include "storage/aio_internal.h"
 #include "storage/fd.h"
+#include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
 #include "storage/lwlock.h"
@@ -277,6 +278,26 @@ pgaio_uring_shmem_size(void)
 	return sz;
 }
 
+/*
+ * on_shmem_exit() callback that releases the io_uring queues in
+ * pgaio_uring_shmem_init.
+ */
+static void
+pgaio_uring_die(int code, Datum arg)
+{
+	if (pgaio_uring_contexts != NULL)
+	{
+		int			TotalProcs = pgaio_uring_procs();
+
+		elog(DEBUG1, "cleaning up %d io_uring processes", TotalProcs);
+
+		for (int i = 0; i < TotalProcs; i++)
+			io_uring_queue_exit(&pgaio_uring_contexts[i].io_uring_ring);
+
+		pgaio_uring_contexts = NULL;
+	}
+}
+
 static void
 pgaio_uring_shmem_init(bool first_time)
 {
@@ -393,6 +414,8 @@ pgaio_uring_shmem_init(bool first_time)
 
 		LWLockInitialize(&context->completion_lock, LWTRANCHE_AIO_URING_COMPLETION);
 	}
+
+	on_shmem_exit(pgaio_uring_die, 0);
 }
 
 static void
-- 
2.53.0

Reply via email to