From d3197f4e6fb6a24d5ea35448b521edeca9717aeb Mon Sep 17 00:00:00 2001
From: Nazir Bilal Yavuz <byavuz81@gmail.com>
Date: Tue, 11 Aug 2026 10:40:14 +0300
Subject: [PATCH v1 1/4] aio: Add fsync support

The AIO subsystem currently supports only reads and writes (writes are
not used yet). Add PGAIO_OP_FSYNC so callers can submit fsync() and
fdatasync() operations through AIO. This allows callers such as the
checkpointer to keep multiple syncs in flight instead of waiting for
each one in turn.

Let callers select the wait event because fsync targets can represent
different kinds of files. The process that performs the operation
reports that event; io_uring uses the generic AIO wait events because
the kernel performs the operation. (Also see [1])

No callers are converted in this commit; subsequent commits do that.

[1] https://postgr.es/m/CAN55FZ0Rp+94rdQ-zeTX0sF9H1e4uWZxH8r1cVXZX0swfqww3g@mail.gmail.com
---
 src/backend/storage/aio/aio_funcs.c       |  4 ++++
 src/backend/storage/aio/aio_io.c          | 27 +++++++++++++++++++++++
 src/backend/storage/aio/method_io_uring.c |  6 +++++
 src/backend/storage/smgr/smgr.c           |  3 +++
 src/include/storage/aio.h                 | 14 ++++++++++--
 5 files changed, 52 insertions(+), 2 deletions(-)

diff --git a/src/backend/storage/aio/aio_funcs.c b/src/backend/storage/aio/aio_funcs.c
index bcdd82318f7..2719556e858 100644
--- a/src/backend/storage/aio/aio_funcs.c
+++ b/src/backend/storage/aio/aio_funcs.c
@@ -191,6 +191,10 @@ retry:
 				values[6] =
 					Int64GetDatum(iov_byte_length(iov_copy, ioh_copy.op_data.write.iov_length));
 				break;
+			case PGAIO_OP_FSYNC:
+				nulls[5] = true;
+				nulls[6] = true;
+				break;
 		}
 
 		/* column: IO's target */
diff --git a/src/backend/storage/aio/aio_io.c b/src/backend/storage/aio/aio_io.c
index 132868130e7..324fb6911e2 100644
--- a/src/backend/storage/aio/aio_io.c
+++ b/src/backend/storage/aio/aio_io.c
@@ -100,6 +100,19 @@ pgaio_io_start_writev(PgAioHandle *ioh,
 	pgaio_io_stage(ioh, PGAIO_OP_WRITEV);
 }
 
+void
+pgaio_io_start_fsync(PgAioHandle *ioh,
+					 int fd, bool datasync, uint32 wait_event_info)
+{
+	pgaio_io_before_start(ioh);
+
+	ioh->op_data.fsync.fd = fd;
+	ioh->op_data.fsync.datasync = datasync;
+	ioh->op_data.fsync.wait_event_info = wait_event_info;
+
+	pgaio_io_stage(ioh, PGAIO_OP_FSYNC);
+}
+
 
 
 /* --------------------------------------------------------------------------------
@@ -137,6 +150,14 @@ pgaio_io_perform_synchronously(PgAioHandle *ioh)
 								ioh->op_data.write.offset);
 			pgstat_report_wait_end();
 			break;
+		case PGAIO_OP_FSYNC:
+			pgstat_report_wait_start(ioh->op_data.fsync.wait_event_info);
+			if (ioh->op_data.fsync.datasync)
+				result = pg_fdatasync(ioh->op_data.fsync.fd);
+			else
+				result = pg_fsync(ioh->op_data.fsync.fd);
+			pgstat_report_wait_end();
+			break;
 		case PGAIO_OP_INVALID:
 			elog(ERROR, "trying to execute invalid IO operation");
 	}
@@ -189,6 +210,8 @@ pgaio_io_get_op_name(PgAioHandle *ioh)
 			return "readv";
 		case PGAIO_OP_WRITEV:
 			return "writev";
+		case PGAIO_OP_FSYNC:
+			return "fsync";
 	}
 
 	return NULL;				/* silence compiler */
@@ -209,6 +232,8 @@ pgaio_io_uses_fd(PgAioHandle *ioh, int fd)
 			return ioh->op_data.read.fd == fd;
 		case PGAIO_OP_WRITEV:
 			return ioh->op_data.write.fd == fd;
+		case PGAIO_OP_FSYNC:
+			return ioh->op_data.fsync.fd == fd;
 		case PGAIO_OP_INVALID:
 			return false;
 	}
@@ -233,6 +258,8 @@ pgaio_io_get_iovec_length(PgAioHandle *ioh, struct iovec **iov)
 			return ioh->op_data.read.iov_length;
 		case PGAIO_OP_WRITEV:
 			return ioh->op_data.write.iov_length;
+		case PGAIO_OP_FSYNC:
+			return 0;
 		default:
 			pg_unreachable();
 			return 0;
diff --git a/src/backend/storage/aio/method_io_uring.c b/src/backend/storage/aio/method_io_uring.c
index 3ffe5061a20..675a91fb5f7 100644
--- a/src/backend/storage/aio/method_io_uring.c
+++ b/src/backend/storage/aio/method_io_uring.c
@@ -802,6 +802,12 @@ pgaio_uring_sq_from_io(PgAioHandle *ioh, struct io_uring_sqe *sqe)
 
 			break;
 
+		case PGAIO_OP_FSYNC:
+			io_uring_prep_fsync(sqe,
+								ioh->op_data.fsync.fd,
+								ioh->op_data.fsync.datasync ? IORING_FSYNC_DATASYNC : 0);
+			break;
+
 		case PGAIO_OP_INVALID:
 			elog(ERROR, "trying to prepare invalid IO operation for execution");
 	}
diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c
index 5391640d861..69e61ea1661 100644
--- a/src/backend/storage/smgr/smgr.c
+++ b/src/backend/storage/smgr/smgr.c
@@ -1094,6 +1094,9 @@ smgr_aio_reopen(PgAioHandle *ioh)
 			od->write.fd = smgrfd(reln, sd->smgr.forkNum, sd->smgr.blockNum, &off);
 			Assert(off == od->write.offset);
 			break;
+		case PGAIO_OP_FSYNC:
+			od->fsync.fd = smgrfd(reln, sd->smgr.forkNum, sd->smgr.blockNum, &off);
+			break;
 	}
 }
 
diff --git a/src/include/storage/aio.h b/src/include/storage/aio.h
index ec543b78409..a1d5f49e51a 100644
--- a/src/include/storage/aio.h
+++ b/src/include/storage/aio.h
@@ -91,10 +91,10 @@ typedef enum PgAioOp
 
 	PGAIO_OP_READV,
 	PGAIO_OP_WRITEV,
+	PGAIO_OP_FSYNC,
 
 	/**
 	 * In the near term we'll need at least:
-	 * - fsync / fdatasync
 	 * - flush_range
 	 *
 	 * Eventually we'll additionally want at least:
@@ -104,7 +104,7 @@ typedef enum PgAioOp
 	 **/
 } PgAioOp;
 
-#define PGAIO_OP_COUNT	(PGAIO_OP_WRITEV + 1)
+#define PGAIO_OP_COUNT	(PGAIO_OP_FSYNC + 1)
 
 
 /*
@@ -146,6 +146,13 @@ typedef union
 		uint16		iov_length;
 		uint64		offset;
 	}			write;
+
+	struct
+	{
+		int			fd;
+		bool		datasync;
+		uint32		wait_event_info;
+	}			fsync;
 } PgAioOpData;
 
 
@@ -300,6 +307,9 @@ extern void pgaio_io_start_readv(PgAioHandle *ioh,
 								 int fd, int iovcnt, uint64 offset);
 extern void pgaio_io_start_writev(PgAioHandle *ioh,
 								  int fd, int iovcnt, uint64 offset);
+extern void pgaio_io_start_fsync(PgAioHandle *ioh, int fd, bool datasync,
+								 uint32 wait_event_info);
+
 
 /* functions in aio_target.c */
 extern void pgaio_io_set_target(PgAioHandle *ioh, PgAioTargetID targetid);
-- 
2.47.3

