From ef979e23f2c0c0a9c238dad1f21f1309b8c5952f Mon Sep 17 00:00:00 2001
From: Nazir Bilal Yavuz <byavuz81@gmail.com>
Date: Tue, 25 Aug 2026 15:00:40 +0300
Subject: [PATCH v2 7/8] aio: Allow IO workers to execute SLRU fsyncs

The SLRU fsyncs introduced by the preceding commit use the generic
PGAIO_TID_SYNC target. It does not store paths in shared memory, so I/O
workers cannot reopen the files and the checkpointer must perform the
operations itself.

Add PGAIO_TID_SYNC_FILETAG, which identifies a file by the FileTag
registered with sync.c. A FileTag contains enough information to
reopen the file in another process. Dispatch reopening through an
optional SyncOps callback, implemented for SLRUs using the new
SlruOpenFileTag(). SLRUs that are not registered with sync.c continue
to use PGAIO_TID_SYNC.

Define the shared FileTag representation in storage/aio_types.h so
PgAioTargetData can store it directly without duplicating its layout.
Describe the target using the SLRU name and segment number, and return
worker-side open failures to the checkpointer as negative errno values.

Like relation fsyncs, the FileTag target opens a transient descriptor
for each operation. Use the existing PgAioTargetInfo close callback to
release it after execution, and document that the callback runs in a
critical section.

SLRU_FLUSH_SYNC is now reported by the I/O worker rather than the
checkpointer, as for relation fsyncs. SyncDataDirectory() continues to
use PGAIO_TID_SYNC because its files have no FileTag.

Discussion: https://postgr.es/m/CAN55FZ0vLWJQNB%3DHuHXG2wabFjXJd6OWTa3%3DkRzwObdZD9poHQ%40mail.gmail.com
---
 src/backend/access/transam/clog.c      |   8 +-
 src/backend/access/transam/commit_ts.c |   8 +-
 src/backend/access/transam/multixact.c |  16 +++-
 src/backend/access/transam/slru.c      |  32 ++++++-
 src/backend/storage/aio/aio_target.c   |   2 +
 src/backend/storage/sync/sync.c        | 123 ++++++++++++++++++++++++-
 src/include/access/clog.h              |   1 +
 src/include/access/commit_ts.h         |   1 +
 src/include/access/multixact.h         |   2 +
 src/include/access/slru.h              |   1 +
 src/include/storage/aio.h              |   9 +-
 src/include/storage/aio_types.h        |  14 +++
 src/include/storage/sync.h             |  19 ++--
 src/tools/pgindent/typedefs.list       |   1 +
 14 files changed, 213 insertions(+), 24 deletions(-)

diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 89fb77ea5da..0fe0491d877 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -1115,10 +1115,16 @@ clog_redo(XLogReaderState *record)
 }
 
 /*
- * Entrypoint for sync.c to sync clog files.
+ * Entrypoints for sync.c to sync and reopen clog files.
  */
 void
 clogsyncfiletag(struct PgAioHandle *ioh, InflightSyncEntry *entry)
 {
 	SlruSyncFileTag(XactCtl, ioh, entry);
 }
+
+int
+clogopenfiletag(const FileTag *ftag)
+{
+	return SlruOpenFileTag(XactCtl, ftag);
+}
diff --git a/src/backend/access/transam/commit_ts.c b/src/backend/access/transam/commit_ts.c
index 7cbbad383b2..4bb95e415ac 100644
--- a/src/backend/access/transam/commit_ts.c
+++ b/src/backend/access/transam/commit_ts.c
@@ -1026,10 +1026,16 @@ commit_ts_redo(XLogReaderState *record)
 }
 
 /*
- * Entrypoint for sync.c to sync commit_ts files.
+ * Entrypoints for sync.c to sync and reopen commit_ts files.
  */
 void
 committssyncfiletag(struct PgAioHandle *ioh, InflightSyncEntry *entry)
 {
 	SlruSyncFileTag(CommitTsCtl, ioh, entry);
 }
+
+int
+committsopenfiletag(const FileTag *ftag)
+{
+	return SlruOpenFileTag(CommitTsCtl, ftag);
+}
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index deb0b110ee8..8054a60afba 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -3001,7 +3001,7 @@ multixact_redo(XLogReaderState *record)
 }
 
 /*
- * Entrypoint for sync.c to sync offsets files.
+ * Entrypoints for sync.c to sync and reopen offsets files.
  */
 void
 multixactoffsetssyncfiletag(struct PgAioHandle *ioh, InflightSyncEntry *entry)
@@ -3009,11 +3009,23 @@ multixactoffsetssyncfiletag(struct PgAioHandle *ioh, InflightSyncEntry *entry)
 	SlruSyncFileTag(MultiXactOffsetCtl, ioh, entry);
 }
 
+int
+multixactoffsetsopenfiletag(const FileTag *ftag)
+{
+	return SlruOpenFileTag(MultiXactOffsetCtl, ftag);
+}
+
 /*
- * Entrypoint for sync.c to sync members files.
+ * Entrypoints for sync.c to sync and reopen members files.
  */
 void
 multixactmemberssyncfiletag(struct PgAioHandle *ioh, InflightSyncEntry *entry)
 {
 	SlruSyncFileTag(MultiXactMemberCtl, ioh, entry);
 }
+
+int
+multixactmembersopenfiletag(const FileTag *ftag)
+{
+	return SlruOpenFileTag(MultiXactMemberCtl, ftag);
+}
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index b1e513ac3b6..058a58baa52 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -1897,11 +1897,16 @@ SlruSyncFileTag(SlruDesc *ctl, struct PgAioHandle *ioh, InflightSyncEntry *entry
 	}
 
 	/*
-	 * Use the generic sync target.  SLRU segments are not smgr relations and
-	 * cannot be reopened from a FileTag in another process, so this fsync
-	 * will run synchronously in worker mode.
+	 * If this SLRU is registered with sync.c, identify the file by its
+	 * FileTag, so that the fsync can be executed by an IO worker, which will
+	 * reopen the file with SlruOpenFileTag().  Otherwise there is no handler
+	 * to reopen the file through, so use the generic sync target, whose IOs
+	 * cannot be handed off to a worker.
 	 */
-	pgaio_io_set_target(ioh, PGAIO_TID_SYNC);
+	if (ctl->options.sync_handler != SYNC_HANDLER_NONE)
+		pgaio_io_set_target_sync_filetag(ioh, &entry->tag);
+	else
+		pgaio_io_set_target(ioh, PGAIO_TID_SYNC);
 
 	/* Start the asynchronous fsync; the fd is closed once it completes. */
 	pgaio_io_start_fsync(ioh, fd, false, WAIT_EVENT_SLRU_FLUSH_SYNC);
@@ -1910,3 +1915,22 @@ SlruSyncFileTag(SlruDesc *ctl, struct PgAioHandle *ioh, InflightSyncEntry *entry
 	entry->close_method = SYNC_CLOSE_TRANSIENT;
 	entry->close_file = fd;
 }
+
+/*
+ * Counterpart to SlruSyncFileTag(), opening the segment identified by ftag in
+ * a process that did not stage the IO.  As with SlruSyncFileTag(), individual
+ * SLRUs have to provide the handler function, so that the correct SlruDesc
+ * is used.
+ *
+ * Returns a file descriptor opened with OpenTransientFile(), or -1 with errno
+ * set.
+ */
+int
+SlruOpenFileTag(SlruDesc *ctl, const FileTag *ftag)
+{
+	char		path[MAXPGPATH];
+
+	SlruFileName(ctl, path, ftag->segno);
+
+	return OpenTransientFile(path, O_RDWR | PG_BINARY);
+}
diff --git a/src/backend/storage/aio/aio_target.c b/src/backend/storage/aio/aio_target.c
index 534382b423b..a62c194c8f2 100644
--- a/src/backend/storage/aio/aio_target.c
+++ b/src/backend/storage/aio/aio_target.c
@@ -17,6 +17,7 @@
 #include "storage/aio.h"
 #include "storage/aio_internal.h"
 #include "storage/smgr.h"
+#include "storage/sync.h"
 
 static char *pgaio_sync_describe_identity(const PgAioTargetData *sd);
 
@@ -39,6 +40,7 @@ static const PgAioTargetInfo *pgaio_target_info[] = {
 	},
 	[PGAIO_TID_SMGR] = &aio_smgr_target_info,
 	[PGAIO_TID_SYNC] = &aio_sync_target_info,
+	[PGAIO_TID_SYNC_FILETAG] = &aio_sync_filetag_target_info,
 };
 
 /*
diff --git a/src/backend/storage/sync/sync.c b/src/backend/storage/sync/sync.c
index 2442492d6a9..80ba569f50b 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -132,10 +132,20 @@ static CycleCtr checkpoint_cycle_ctr = 0;
 typedef struct SyncOps
 {
 	void		(*sync_syncfiletag) (PgAioHandle *ioh, InflightSyncEntry *entry);
+
+	/*
+	 * Optional.  Reopen the file identified by ftag, so that an fsync started
+	 * by sync_syncfiletag() can be executed in a different process, e.g. an
+	 * IO worker.  Returns a file descriptor opened with OpenTransientFile(),
+	 * or -1 with errno set.  Handlers that provide this must use the
+	 * PGAIO_TID_SYNC_FILETAG target (see pgaio_io_set_target_sync_filetag()).
+	 */
+	int			(*sync_openfiletag) (const FileTag *ftag);
 	int			(*sync_unlinkfiletag) (const FileTag *ftag, char *path);
 	bool		(*sync_filetagmatches) (const FileTag *ftag,
 										const FileTag *candidate);
 	bool		uses_transient_fd;
+	const char *sync_target_name;
 } SyncOps;
 
 /*
@@ -152,25 +162,130 @@ static const SyncOps syncsw[] = {
 	/* pg_xact */
 	[SYNC_HANDLER_CLOG] = {
 		.sync_syncfiletag = clogsyncfiletag,
-		.uses_transient_fd = true
+		.sync_openfiletag = clogopenfiletag,
+		.uses_transient_fd = true,
+		.sync_target_name = "pg_xact"
 	},
 	/* pg_commit_ts */
 	[SYNC_HANDLER_COMMIT_TS] = {
 		.sync_syncfiletag = committssyncfiletag,
-		.uses_transient_fd = true
+		.sync_openfiletag = committsopenfiletag,
+		.uses_transient_fd = true,
+		.sync_target_name = "pg_commit_ts"
 	},
 	/* pg_multixact/offsets */
 	[SYNC_HANDLER_MULTIXACT_OFFSET] = {
 		.sync_syncfiletag = multixactoffsetssyncfiletag,
-		.uses_transient_fd = true
+		.sync_openfiletag = multixactoffsetsopenfiletag,
+		.uses_transient_fd = true,
+		.sync_target_name = "pg_multixact/offsets"
 	},
 	/* pg_multixact/members */
 	[SYNC_HANDLER_MULTIXACT_MEMBER] = {
 		.sync_syncfiletag = multixactmemberssyncfiletag,
-		.uses_transient_fd = true
+		.sync_openfiletag = multixactmembersopenfiletag,
+		.uses_transient_fd = true,
+		.sync_target_name = "pg_multixact/members"
 	}
 };
 
+static int	sync_aio_reopen(PgAioHandle *ioh);
+static void sync_aio_close(PgAioHandle *ioh);
+static char *sync_aio_describe_identity(const PgAioTargetData *sd);
+
+/*
+ * Target info for files identified by a FileTag (see PGAIO_TID_SYNC_FILETAG).
+ * Unlike PGAIO_TID_SYNC, a FileTag contains everything needed to find the
+ * file again in another process, so such IOs can be executed by IO workers.
+ */
+const PgAioTargetInfo aio_sync_filetag_target_info = {
+	.name = "sync_filetag",
+	.reopen = sync_aio_reopen,
+	.close = sync_aio_close,
+	.describe_identity = sync_aio_describe_identity,
+};
+
+/*
+ * Set up ioh to operate on the file identified by ftag.
+ */
+void
+pgaio_io_set_target_sync_filetag(PgAioHandle *ioh, const FileTag *ftag)
+{
+	PgAioTargetData *sd = pgaio_io_get_target_data(ioh);
+
+	Assert(syncsw[ftag->handler].sync_openfiletag != NULL);
+	Assert(syncsw[ftag->handler].sync_target_name != NULL);
+
+	pgaio_io_set_target(ioh, PGAIO_TID_SYNC_FILETAG);
+
+	sd->sync_filetag = *ftag;
+}
+
+static FileTag
+sync_aio_filetag(const PgAioTargetData *sd)
+{
+	return sd->sync_filetag;
+}
+
+/*
+ * reopen callback for PGAIO_TID_SYNC_FILETAG, to open the file in the process
+ * executing the IO.
+ */
+static int
+sync_aio_reopen(PgAioHandle *ioh)
+{
+	PgAioTargetData *sd = pgaio_io_get_target_data(ioh);
+	PgAioOpData *od = pgaio_io_get_op_data(ioh);
+	FileTag		ftag = sync_aio_filetag(sd);
+	int			fd;
+
+	/*
+	 * The caller needs to prevent interrupts from being processed, otherwise
+	 * the FD could be closed again before we get to executing the IO.
+	 */
+	Assert(!INTERRUPTS_CAN_BE_PROCESSED());
+
+	Assert(pgaio_io_get_op(ioh) == PGAIO_OP_FSYNC);
+
+	fd = syncsw[ftag.handler].sync_openfiletag(&ftag);
+	if (fd < 0)
+		return -errno;
+
+	od->fsync.fd = fd;
+
+	return 0;
+}
+
+/*
+ * close callback for PGAIO_TID_SYNC_FILETAG, releasing the descriptor
+ * acquired by sync_aio_reopen().
+ *
+ * Called in a critical section after the fsync result has been saved for
+ * the issuer.  Do not report close failures here: this descriptor was only
+ * used for fsync, and any fsync failure is handled by the issuer.
+ */
+static void
+sync_aio_close(PgAioHandle *ioh)
+{
+	PgAioOpData *od = pgaio_io_get_op_data(ioh);
+
+	(void) CloseTransientFile(od->fsync.fd);
+	od->fsync.fd = -1;
+}
+
+/*
+ * describe_identity callback for PGAIO_TID_SYNC_FILETAG.
+ */
+static char *
+sync_aio_describe_identity(const PgAioTargetData *sd)
+{
+	FileTag		ftag = sync_aio_filetag(sd);
+
+	return psprintf(_("segment " UINT64_FORMAT " of SLRU \"%s\""),
+					ftag.segno,
+					syncsw[ftag.handler].sync_target_name);
+}
+
 /*
  * Initialize data structures for the file sync tracking.
  */
diff --git a/src/include/access/clog.h b/src/include/access/clog.h
index e089106f7fe..fc06794d186 100644
--- a/src/include/access/clog.h
+++ b/src/include/access/clog.h
@@ -48,6 +48,7 @@ extern void ExtendCLOG(TransactionId newestXact);
 extern void TruncateCLOG(TransactionId oldestXact, Oid oldestxid_datoid);
 
 extern void clogsyncfiletag(PgAioHandle *ioh, InflightSyncEntry *entry);
+extern int	clogopenfiletag(const FileTag *ftag);
 
 /* XLOG stuff */
 #define CLOG_ZEROPAGE		0x00
diff --git a/src/include/access/commit_ts.h b/src/include/access/commit_ts.h
index fa4880e0d03..ebb87d9534e 100644
--- a/src/include/access/commit_ts.h
+++ b/src/include/access/commit_ts.h
@@ -39,6 +39,7 @@ extern void SetCommitTsLimit(TransactionId oldestXact,
 extern void AdvanceOldestCommitTsXid(TransactionId oldestXact);
 
 extern void committssyncfiletag(PgAioHandle *ioh, InflightSyncEntry *entry);
+extern int	committsopenfiletag(const FileTag *ftag);
 
 /* XLOG stuff */
 #define COMMIT_TS_ZEROPAGE		0x00
diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h
index 3f980b4120d..3f5233a3bcd 100644
--- a/src/include/access/multixact.h
+++ b/src/include/access/multixact.h
@@ -115,7 +115,9 @@ extern bool MultiXactIdPrecedesOrEquals(MultiXactId multi1,
 										MultiXactId multi2);
 
 extern void multixactoffsetssyncfiletag(PgAioHandle *ioh, InflightSyncEntry *entry);
+extern int	multixactoffsetsopenfiletag(const FileTag *ftag);
 extern void multixactmemberssyncfiletag(PgAioHandle *ioh, InflightSyncEntry *entry);
+extern int	multixactmembersopenfiletag(const FileTag *ftag);
 
 extern void AtEOXact_MultiXact(void);
 extern void AtPrepare_MultiXact(void);
diff --git a/src/include/access/slru.h b/src/include/access/slru.h
index 0e91df5609c..43429392c7d 100644
--- a/src/include/access/slru.h
+++ b/src/include/access/slru.h
@@ -241,6 +241,7 @@ extern bool SlruScanDirectory(SlruDesc *ctl, SlruScanCallback callback, void *da
 extern void SlruDeleteSegment(SlruDesc *ctl, int64 segno);
 
 extern void SlruSyncFileTag(SlruDesc *ctl, struct PgAioHandle *ioh, struct InflightSyncEntry *entry);
+extern int	SlruOpenFileTag(SlruDesc *ctl, const FileTag *ftag);
 
 /* SlruScanDirectory public callbacks */
 extern bool SlruScanDirCbReportPresence(SlruDesc *ctl, char *filename,
diff --git a/src/include/storage/aio.h b/src/include/storage/aio.h
index 6ea230ccad5..1f32314f2b6 100644
--- a/src/include/storage/aio.h
+++ b/src/include/storage/aio.h
@@ -119,9 +119,10 @@ typedef enum PgAioTargetID
 	PGAIO_TID_INVALID = 0,
 	PGAIO_TID_SMGR,
 	PGAIO_TID_SYNC,
+	PGAIO_TID_SYNC_FILETAG,
 } PgAioTargetID;
 
-#define PGAIO_TID_COUNT (PGAIO_TID_SYNC + 1)
+#define PGAIO_TID_COUNT (PGAIO_TID_SYNC_FILETAG + 1)
 
 
 /*
@@ -178,7 +179,11 @@ struct PgAioTargetInfo
 
 	/*
 	 * Optional counterpart to reopen, releasing the file descriptor it
-	 * acquired once the IO has been executed.
+	 * acquired.  Called in the process that reopened the IO, after the IO has
+	 * been executed.  Targets whose reopen callback reuses a cached
+	 * descriptor do not need this.
+	 *
+	 * This is called in a critical section, so it must not raise errors.
 	 */
 	void		(*close) (PgAioHandle *ioh);
 
diff --git a/src/include/storage/aio_types.h b/src/include/storage/aio_types.h
index 17b59aeed7c..90bc4c73989 100644
--- a/src/include/storage/aio_types.h
+++ b/src/include/storage/aio_types.h
@@ -23,6 +23,18 @@ typedef struct PgAioHandle PgAioHandle;
 typedef struct PgAioHandleCallbacks PgAioHandleCallbacks;
 typedef struct PgAioTargetInfo PgAioTargetInfo;
 
+/*
+ * A tag identifying a file handled by sync.c.  This is defined here so that
+ * PgAioTargetData can store it without duplicating its representation.
+ */
+typedef struct PgAioSyncFileTag
+{
+	int16		handler;		/* SyncRequestHandler value */
+	int16		forknum;		/* ForkNumber */
+	RelFileLocator rlocator;	/* physical relation identifier */
+	uint64		segno;
+} PgAioSyncFileTag;
+
 /*
  * A reference to an IO that can be used to wait for the IO (using
  * pgaio_wref_wait()) to complete.
@@ -69,6 +81,8 @@ typedef union PgAioTargetData
 		bool		is_temp:1;	/* proc can be inferred by owning AIO */
 		bool		skip_fsync:1;
 	}			smgr;
+
+	PgAioSyncFileTag sync_filetag;
 } PgAioTargetData;
 
 
diff --git a/src/include/storage/sync.h b/src/include/storage/sync.h
index 8d48151730f..e85c97c2ba4 100644
--- a/src/include/storage/sync.h
+++ b/src/include/storage/sync.h
@@ -46,17 +46,11 @@ typedef enum SyncRequestHandler
 } SyncRequestHandler;
 
 /*
- * A tag identifying a file.  Currently it has the members required for md.c's
- * usage, but sync.c has no knowledge of the internal structure, and it is
- * liable to change as required by future handlers.
+ * A tag identifying a file.  Its representation is shared with AIO target
+ * data, so changes are automatically visible to processes that reopen files
+ * on behalf of sync.c.
  */
-typedef struct FileTag
-{
-	int16		handler;		/* SyncRequestHandler value, saving space */
-	int16		forknum;		/* ForkNumber, saving space */
-	RelFileLocator rlocator;
-	uint64		segno;
-} FileTag;
+typedef PgAioSyncFileTag FileTag;
 
 struct PendingFsyncEntry;
 struct PgAioHandle;
@@ -118,4 +112,9 @@ extern void RememberSyncRequest(const FileTag *ftag, SyncRequestType type);
 extern bool RegisterSyncRequest(const FileTag *ftag, SyncRequestType type,
 								bool retryOnError);
 
+/* AIO support */
+extern PGDLLIMPORT const PgAioTargetInfo aio_sync_filetag_target_info;
+extern void pgaio_io_set_target_sync_filetag(PgAioHandle *ioh,
+											 const FileTag *ftag);
+
 #endif							/* SYNC_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c5ed5bc6c0f..5e09f3c2a06 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2261,6 +2261,7 @@ PgAioOpData
 PgAioResult
 PgAioResultStatus
 PgAioReturn
+PgAioSyncFileTag
 PgAioTargetData
 PgAioTargetID
 PgAioTargetInfo
-- 
2.47.3

