v2-0001-Make-sync.c-syncsw-extensible-via-register_sync_h.patch
text/x-patch
Filename: v2-0001-Make-sync.c-syncsw-extensible-via-register_sync_h.patch
Type: text/x-patch
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v2-0001
Subject: Make sync.c syncsw[] extensible via register_sync_handler()
| File | + | − |
|---|---|---|
| doc/src/sgml/custom-sync-handler.sgml | 118 | 0 |
| doc/src/sgml/filelist.sgml | 1 | 0 |
| doc/src/sgml/postgres.sgml | 1 | 0 |
| src/backend/postmaster/postmaster.c | 11 | 0 |
| src/backend/storage/sync/sync.c | 240 | 38 |
| src/include/storage/sync.h | 61 | 3 |
From cd1cd0e668d6f7d7f315fd2e6aa41b81c9a4b724 Mon Sep 17 00:00:00 2001
From: Greg Lamberson <greg@lamco.io>
Date: Fri, 10 Apr 2026 07:27:14 -0500
Subject: [PATCH v2 1/2] Make sync.c syncsw[] extensible via
register_sync_handler()
Introduce a public extension API, register_sync_handler(), that lets
extensions install their own entries in the sync.c dispatch table.
This enables storage-related extensions to participate in the
checkpoint fsync pipeline without faking md.c segments or bypassing
sync.c's request coalescing and cancellation machinery.
The previously static syncsw[] array becomes a heap-allocated
dispatch table populated in two phases: the five built-in handlers
(MD, CLOG, commit_ts, multixact_offset, multixact_member) are
registered via InitSyncHandlers() before process_shared_preload_-
libraries(), and extension _PG_init() calls receive sequentially
assigned IDs starting at SYNC_HANDLER_FIRST_DYNAMIC. Registration
is forbidden after process_shared_preload_libraries_done is set.
InitSyncHandlers() is called from both PostmasterMain() (for the
fork() path) and from register_sync_handler() itself (for the
EXEC_BACKEND path, where each child re-runs shared_preload_libraries
in its own address space and may reach an extension's registration
call before it has called InitSync()). An explicit
builtin_sync_handlers_registered flag guards against repeated
built-in registration.
SYNC_HANDLER_NONE is changed from its previous implicit value of 5
to an explicit -1 so that the "no handler" sentinel cannot be
confused with a valid handler index. The only consumers in
core are value-agnostic != comparisons in slru.c.
Documentation: doc/src/sgml/custom-sync-handler.sgml, modeled on
doc/src/sgml/custom-rmgr.sgml.
Discussion: https://postgr.es/m/IA1PR07MB983072521EE7FDEE98902534A9592@IA1PR07MB9830.namprd07.prod.outlook.com
---
doc/src/sgml/custom-sync-handler.sgml | 118 +++++++++++
doc/src/sgml/filelist.sgml | 1 +
doc/src/sgml/postgres.sgml | 1 +
src/backend/postmaster/postmaster.c | 11 +
src/backend/storage/sync/sync.c | 278 ++++++++++++++++++++++----
src/include/storage/sync.h | 64 +++++-
6 files changed, 432 insertions(+), 41 deletions(-)
create mode 100644 doc/src/sgml/custom-sync-handler.sgml
diff --git a/doc/src/sgml/custom-sync-handler.sgml b/doc/src/sgml/custom-sync-handler.sgml
new file mode 100644
index 00000000000..6d95efe7440
--- /dev/null
+++ b/doc/src/sgml/custom-sync-handler.sgml
@@ -0,0 +1,118 @@
+<!-- doc/src/sgml/custom-sync-handler.sgml -->
+
+<chapter id="custom-sync-handler">
+ <title>Custom Sync Handlers for Extensions</title>
+
+ <para>
+ This chapter explains the interface between the core
+ <productname>PostgreSQL</productname> system and custom sync handlers,
+ which enable extensions to participate in the checkpoint
+ <function>fsync</function> pipeline implemented in
+ <filename>src/backend/storage/sync/sync.c</filename>.
+ </para>
+
+ <para>
+ Extensions that manage storage outside the standard relation-file layout,
+ such as a <link linkend="tableam">Table Access Method</link> that stores
+ its data in a non-file format, may need their data to be
+ <function>fsync</function>ed at checkpoint time in the same manner as the
+ built-in handlers do for relation segments, <acronym>CLOG</acronym>,
+ <structname>commit_ts</structname>, and multixact data. A custom sync
+ handler lets an extension register its own sync callback, participate in
+ the same request coalescing and cancellation mechanisms, and benefit from
+ the checkpointer's batching and <varname>cycle_ctr</varname> semantics
+ without reimplementing the machinery or faking its data as
+ <function>md.c</function> segments.
+ </para>
+
+ <para>
+ To create a custom sync handler, first define a
+ <structname>SyncOps</structname> structure containing the handler
+ callbacks. The structure is defined in
+ <filename>src/include/storage/sync.h</filename>:
+<programlisting>
+typedef struct SyncOps
+{
+ int (*sync_syncfiletag) (const FileTag *ftag, char *path);
+ int (*sync_unlinkfiletag) (const FileTag *ftag, char *path);
+ bool (*sync_filetagmatches) (const FileTag *ftag,
+ const FileTag *candidate);
+} SyncOps;
+</programlisting>
+ Only <structfield>sync_syncfiletag</structfield> is required; the other
+ two pointers may be <literal>NULL</literal> if the handler does not
+ participate in <literal>SYNC_UNLINK_REQUEST</literal> or
+ <literal>SYNC_FILTER_REQUEST</literal> flows. This mirrors the built-in
+ handlers for <acronym>CLOG</acronym>, <structname>commit_ts</structname>,
+ and multixact data, which only define
+ <structfield>sync_syncfiletag</structfield>.
+ </para>
+
+ <para>
+ Then, register the handler and record the returned handler ID:
+<programlisting>
+extern int16 register_sync_handler(const SyncOps *ops, const char *name);
+</programlisting>
+ <function>register_sync_handler</function> must be called from the
+ extension module's <link linkend="xfunc-c-dynload">_PG_init</link>
+ function while <varname>shared_preload_libraries</varname> is still being
+ loaded; calls made after that phase has completed raise
+ <literal>FATAL</literal>. The extension must therefore be placed in
+ <xref linkend="guc-shared-preload-libraries"/>.
+ </para>
+
+ <para>
+ The returned <type>int16</type> handler ID is the value the extension
+ stores in <structfield>FileTag.handler</structfield> when queuing sync
+ requests via <function>RegisterSyncRequest</function>. Extension handler
+ IDs are assigned sequentially starting at
+ <literal>SYNC_HANDLER_FIRST_DYNAMIC</literal>, which is the first value
+ after the built-in handler IDs <literal>SYNC_HANDLER_MD</literal>,
+ <literal>SYNC_HANDLER_CLOG</literal>,
+ <literal>SYNC_HANDLER_COMMIT_TS</literal>,
+ <literal>SYNC_HANDLER_MULTIXACT_OFFSET</literal>, and
+ <literal>SYNC_HANDLER_MULTIXACT_MEMBER</literal>. The assigned ID is
+ stable for the lifetime of a given server configuration, that is, it
+ does not change between backends, the checkpointer, or auxiliary
+ processes within a single postmaster lifetime. Because sync requests
+ live only in the checkpointer's in-memory pending-operations hash table
+ and are not persisted across server restarts, the assigned ID does not
+ need to be stable across restarts or across changes to
+ <varname>shared_preload_libraries</varname>.
+ </para>
+
+ <para>
+ The <structname>FileTag</structname> structure passed to the handler
+ callbacks has a small fixed layout that all handlers share. Its
+ contents are opaque to <filename>sync.c</filename>; each handler
+ interprets the fields according to its own convention. Because
+ <filename>sync.c</filename> deduplicates pending sync requests by
+ hashing the raw bytes of the <structname>FileTag</structname>
+ (<literal>HASH_BLOBS</literal>), every field including any padding
+ must be zeroed before the structure is populated, otherwise logically
+ identical tags with different padding bytes will not coalesce into a
+ single callback invocation. A simple <function>memset</function> to
+ zero before assignment is sufficient.
+ </para>
+
+ <para>
+ The <filename>src/test/modules/test_sync_handler</filename> module
+ contains a minimal working example, which demonstrates registration
+ from <function>_PG_init</function>, the per-checkpoint callback
+ dispatch, request coalescing via <literal>HASH_BLOBS</literal>, and
+ the <varname>cycle_ctr</varname> skip behaviour on idle checkpoints.
+ The TAP test in that module also serves as a copy-paste starting point
+ for new sync-handler extensions.
+ </para>
+
+ <note>
+ <para>
+ The extension must remain in <varname>shared_preload_libraries</varname>
+ as long as any data managed by its sync handler may require
+ checkpointing. If the extension is removed while such data exists,
+ <productname>PostgreSQL</productname> will not be able to dispatch
+ pending sync requests for that data, which may lead to durability
+ issues at the next checkpoint.
+ </para>
+ </note>
+</chapter>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 25a85082759..c6a4f1745ae 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -113,6 +113,7 @@
<!ENTITY wal-for-extensions SYSTEM "wal-for-extensions.sgml">
<!ENTITY generic-wal SYSTEM "generic-wal.sgml">
<!ENTITY custom-rmgr SYSTEM "custom-rmgr.sgml">
+<!ENTITY custom-sync-handler SYSTEM "custom-sync-handler.sgml">
<!ENTITY backup-manifest SYSTEM "backup-manifest.sgml">
<!ENTITY oauth-validators SYSTEM "oauth-validators.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2101442c90f..c91877c8dd8 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -259,6 +259,7 @@ break is not needed in a wider output rendering.
&tableam;
&indexam;
&wal-for-extensions;
+ &custom-sync-handler;
&indextypes;
&storage;
&transaction;
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 6e0f41d2661..8d2ab37ce26 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -116,6 +116,7 @@
#include "storage/pmsignal.h"
#include "storage/proc.h"
#include "storage/shmem_internal.h"
+#include "storage/sync.h"
#include "tcop/backend_startup.h"
#include "tcop/tcopprot.h"
#include "utils/datetime.h"
@@ -929,6 +930,16 @@ PostmasterMain(int argc, char *argv[])
*/
RegisterBuiltinShmemCallbacks();
+ /*
+ * Register the built-in sync handlers (md, CLOG, commit_ts,
+ * multixact_offset, multixact_member). This must happen before
+ * process_shared_preload_libraries() so that extensions which
+ * call register_sync_handler() from their _PG_init() receive IDs
+ * starting at SYNC_HANDLER_FIRST_DYNAMIC instead of colliding
+ * with the built-in slots.
+ */
+ InitSyncHandlers();
+
/*
* process any libraries that should be preloaded at postmaster start
*/
diff --git a/src/backend/storage/sync/sync.c b/src/backend/storage/sync/sync.c
index 2c964b6f3d9..ff6239680cf 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -80,50 +80,219 @@ static CycleCtr checkpoint_cycle_ctr = 0;
#define UNLINKS_PER_ABSORB 10
/*
- * Function pointers for handling sync and unlink requests.
+ * Sync handler dispatch table.
+ *
+ * Populated by InitSyncHandlers() for the five built-in handlers (MD,
+ * CLOG, commit_ts, multixact_offset, multixact_member) and by
+ * register_sync_handler() for handlers installed by extensions from
+ * their _PG_init() function. After shared_preload_libraries has
+ * finished loading, syncsw[] is effectively immutable for the life
+ * of the process.
+ *
+ * Every process that can call into sync.c (postmaster, backends, and
+ * the checkpointer and other auxiliary processes) obtains its own
+ * populated syncsw[] either by inheriting it via fork() from the
+ * postmaster, or, on EXEC_BACKEND platforms where there is no fork(),
+ * by re-running InitSyncHandlers() and process_shared_preload_libraries()
+ * in its own address space during startup.
+ *
+ * SyncOps itself is defined in sync.h so that extensions can declare
+ * const SyncOps instances at file scope.
*/
-typedef struct SyncOps
-{
- int (*sync_syncfiletag) (const FileTag *ftag, char *path);
- int (*sync_unlinkfiletag) (const FileTag *ftag, char *path);
- bool (*sync_filetagmatches) (const FileTag *ftag,
- const FileTag *candidate);
-} SyncOps;
+static SyncOps *syncsw = NULL;
+static const char **sync_handler_names = NULL;
+static int NSyncHandlers = 0;
+static int sync_handlers_capacity = 0;
+static bool builtin_sync_handlers_registered = false;
/*
- * These indexes must correspond to the values of the SyncRequestHandler enum.
+ * Built-in SyncOps, registered in enum order during InitSync() so that
+ * SYNC_HANDLER_MD == 0, SYNC_HANDLER_CLOG == 1, etc.
*/
-static const SyncOps syncsw[] = {
- /* magnetic disk */
- [SYNC_HANDLER_MD] = {
- .sync_syncfiletag = mdsyncfiletag,
- .sync_unlinkfiletag = mdunlinkfiletag,
- .sync_filetagmatches = mdfiletagmatches
- },
- /* pg_xact */
- [SYNC_HANDLER_CLOG] = {
- .sync_syncfiletag = clogsyncfiletag
- },
- /* pg_commit_ts */
- [SYNC_HANDLER_COMMIT_TS] = {
- .sync_syncfiletag = committssyncfiletag
- },
- /* pg_multixact/offsets */
- [SYNC_HANDLER_MULTIXACT_OFFSET] = {
- .sync_syncfiletag = multixactoffsetssyncfiletag
- },
- /* pg_multixact/members */
- [SYNC_HANDLER_MULTIXACT_MEMBER] = {
- .sync_syncfiletag = multixactmemberssyncfiletag
- }
+static const SyncOps builtin_md_ops = {
+ .sync_syncfiletag = mdsyncfiletag,
+ .sync_unlinkfiletag = mdunlinkfiletag,
+ .sync_filetagmatches = mdfiletagmatches,
+};
+static const SyncOps builtin_clog_ops = {
+ .sync_syncfiletag = clogsyncfiletag,
+};
+static const SyncOps builtin_committs_ops = {
+ .sync_syncfiletag = committssyncfiletag,
+};
+static const SyncOps builtin_multixact_offset_ops = {
+ .sync_syncfiletag = multixactoffsetssyncfiletag,
+};
+static const SyncOps builtin_multixact_member_ops = {
+ .sync_syncfiletag = multixactmemberssyncfiletag,
};
+/*
+ * Internal helper that adds an entry to syncsw[] without performing the
+ * preload-phase check. Used by InitSync() to install the built-in
+ * handlers, which must be present in every process that calls into
+ * sync.c (including the checkpointer, which runs after
+ * shared_preload_libraries has finished loading).
+ */
+static int16
+sync_handler_register_internal(const SyncOps *ops, const char *name)
+{
+ int16 my_id;
+ MemoryContext old;
+
+ if (ops == NULL || ops->sync_syncfiletag == NULL)
+ elog(FATAL, "sync handler registration requires a non-NULL sync callback");
+
+ if (name == NULL || *name == '\0')
+ elog(FATAL, "sync handler name must not be empty");
+
+ if (NSyncHandlers >= SYNC_HANDLER_MAX)
+ ereport(FATAL,
+ (errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
+ errmsg("too many sync handlers registered (maximum is %d)",
+ SYNC_HANDLER_MAX)));
+
+ old = MemoryContextSwitchTo(TopMemoryContext);
+
+ if (NSyncHandlers >= sync_handlers_capacity)
+ {
+ int new_cap = (sync_handlers_capacity == 0)
+ ? 8
+ : sync_handlers_capacity * 2;
+
+ if (new_cap > SYNC_HANDLER_MAX)
+ new_cap = SYNC_HANDLER_MAX;
+
+ if (syncsw == NULL)
+ {
+ syncsw = palloc(sizeof(SyncOps) * new_cap);
+ sync_handler_names = palloc(sizeof(char *) * new_cap);
+ }
+ else
+ {
+ syncsw = repalloc(syncsw, sizeof(SyncOps) * new_cap);
+ sync_handler_names = repalloc(sync_handler_names,
+ sizeof(char *) * new_cap);
+ }
+ sync_handlers_capacity = new_cap;
+ }
+
+ my_id = (int16) NSyncHandlers++;
+ memcpy(&syncsw[my_id], ops, sizeof(SyncOps));
+ sync_handler_names[my_id] = pstrdup(name);
+
+ MemoryContextSwitchTo(old);
+
+ /*
+ * No barrier needed: registration only happens during
+ * shared_preload_libraries load, which is single-threaded. On fork-based
+ * platforms backends and auxiliary processes inherit the fully-populated
+ * array from the postmaster via fork(). On EXEC_BACKEND platforms each
+ * child repeats the single-threaded registration sequence in its own
+ * address space during startup. In either case, the array is immutable by
+ * the time any concurrent reader can observe it.
+ */
+ return my_id;
+}
+
+/*
+ * Public registration entry point for extensions. See sync.h for the
+ * contract.
+ *
+ * Extensions must call this from their _PG_init() while
+ * shared_preload_libraries is still being processed; later calls raise
+ * FATAL. Built-in handlers bypass this guard via
+ * sync_handler_register_internal() because the checkpointer and other
+ * auxiliary processes call InitSync() after preload has finished, and
+ * the built-in dispatch table must still be populated in those
+ * processes.
+ *
+ * On EXEC_BACKEND platforms each child process repeats
+ * process_shared_preload_libraries() in its own fresh address space
+ * during startup, and an extension's _PG_init() can reach this
+ * function before the child has called InitSync(). Call
+ * InitSyncHandlers() here to ensure the five built-in handlers always
+ * occupy IDs 0..SYNC_HANDLER_FIRST_DYNAMIC-1 before any dynamic ID is
+ * assigned, which keeps handler IDs consistent across every process
+ * that dispatches sync requests. InitSyncHandlers() is idempotent.
+ */
+int16
+register_sync_handler(const SyncOps *ops, const char *name)
+{
+ if (process_shared_preload_libraries_done)
+ ereport(FATAL,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("sync handler registration is only permitted while shared_preload_libraries is being loaded")));
+
+ InitSyncHandlers();
+
+ return sync_handler_register_internal(ops, name);
+}
+
+/*
+ * Register the built-in sync handlers.
+ *
+ * This MUST run before any call to register_sync_handler() from
+ * extension _PG_init() code, so that the built-in handlers occupy
+ * their canonical IDs (SYNC_HANDLER_MD = 0, SYNC_HANDLER_CLOG = 1,
+ * etc.) and extension handlers are assigned IDs >=
+ * SYNC_HANDLER_FIRST_DYNAMIC.
+ *
+ * Called from:
+ * - PostmasterMain(), just before process_shared_preload_libraries()
+ * - AuxiliaryProcessMain() (not currently needed because aux procs
+ * fork from the postmaster with syncsw[] already populated, but
+ * see the idempotent NSyncHandlers==0 guard below)
+ * - Standalone backend init (via InitSync -> InitSyncHandlers)
+ *
+ * Idempotent: the NSyncHandlers == 0 guard ensures built-ins are
+ * registered exactly once per process. Safe to call from multiple
+ * init paths.
+ */
+void
+InitSyncHandlers(void)
+{
+ if (builtin_sync_handlers_registered)
+ return;
+
+ (void) sync_handler_register_internal(&builtin_md_ops, "md");
+ (void) sync_handler_register_internal(&builtin_clog_ops, "clog");
+ (void) sync_handler_register_internal(&builtin_committs_ops, "commit_ts");
+ (void) sync_handler_register_internal(&builtin_multixact_offset_ops,
+ "multixact_offset");
+ (void) sync_handler_register_internal(&builtin_multixact_member_ops,
+ "multixact_member");
+
+ builtin_sync_handlers_registered = true;
+
+ /*
+ * Enforce the enum-to-count invariant: if a new built-in is added to the
+ * SyncRequestHandler enum, the build will fail-fast at first boot until a
+ * matching sync_handler_register_internal() call is added here.
+ */
+ Assert(NSyncHandlers == SYNC_HANDLER_FIRST_DYNAMIC);
+}
+
/*
* Initialize data structures for the file sync tracking.
+ *
+ * This runs in processes that actually need the pendingOps hash table
+ * (standalone backends and the checkpointer). It also calls
+ * InitSyncHandlers() defensively in case this process reached here
+ * without the postmaster having done so, e.g., standalone mode.
*/
void
InitSync(void)
{
+ /*
+ * Make sure built-in handlers are registered. In the postmaster, this was
+ * already called from PostmasterMain() before
+ * process_shared_preload_libraries(); in standalone mode it is called
+ * here for the first (and only) time. The NSyncHandlers guard inside
+ * InitSyncHandlers() makes it idempotent.
+ */
+ InitSyncHandlers();
+
/*
* Create pending-operations hashtable if we need it. Currently, we need
* it if we are standalone (not under a postmaster) or if we are a
@@ -205,6 +374,19 @@ SyncPostCheckpoint(void)
int absorb_counter;
ListCell *lc;
+ /*
+ * Cache the syncsw base pointer in a local for the duration of this
+ * function. Without this, the compiler cannot hoist the load of the
+ * mutable static pointer out of the dispatch loop, and each dispatch
+ * costs an extra memory load plus an address-materialization LEA
+ * (verified with objdump on GCC 14.2 -O2). With the local cached, the
+ * per-entry dispatch compiles down to identical assembly as the pre-patch
+ * static-const array. Safe because register_sync_handler() is forbidden
+ * after process_shared_preload_libraries_done and syncsw is never mutated
+ * outside registration.
+ */
+ SyncOps *ops = syncsw;
+
absorb_counter = UNLINKS_PER_ABSORB;
foreach(lc, pendingUnlinks)
{
@@ -227,9 +409,12 @@ SyncPostCheckpoint(void)
if (entry->cycle_ctr == checkpoint_cycle_ctr)
break;
+ Assert(entry->tag.handler >= 0 &&
+ entry->tag.handler < NSyncHandlers);
+
/* Unlink the file */
- if (syncsw[entry->tag.handler].sync_unlinkfiletag(&entry->tag,
- path) < 0)
+ if (ops[entry->tag.handler].sync_unlinkfiletag(&entry->tag,
+ path) < 0)
{
/*
* There's a race condition, when the database is dropped at the
@@ -301,6 +486,9 @@ ProcessSyncRequests(void)
uint64 longest = 0;
uint64 total_elapsed = 0;
+ /* See comment in SyncPostCheckpoint() above. */
+ SyncOps *ops = syncsw;
+
/*
* This is only called during checkpoints, and checkpoints should only
* occur in processes that have created a pendingOps.
@@ -412,9 +600,12 @@ ProcessSyncRequests(void)
{
char path[MAXPGPATH];
+ Assert(entry->tag.handler >= 0 &&
+ entry->tag.handler < NSyncHandlers);
+
INSTR_TIME_SET_CURRENT(sync_start);
- if (syncsw[entry->tag.handler].sync_syncfiletag(&entry->tag,
- path) == 0)
+ if (ops[entry->tag.handler].sync_syncfiletag(&entry->tag,
+ path) == 0)
{
/* Success; update statistics about sync timing */
INSTR_TIME_SET_CURRENT(sync_end);
@@ -506,13 +697,24 @@ RememberSyncRequest(const FileTag *ftag, SyncRequestType type)
HASH_SEQ_STATUS hstat;
PendingFsyncEntry *pfe;
ListCell *cell;
+ bool (*filetagmatches) (const FileTag *ftag,
+ const FileTag *candidate);
+
+ Assert(ftag->handler >= 0 && ftag->handler < NSyncHandlers);
+
+ /*
+ * Cache the per-handler filetagmatches function pointer once so both
+ * match loops keep it in a register. See comment in
+ * SyncPostCheckpoint().
+ */
+ filetagmatches = syncsw[ftag->handler].sync_filetagmatches;
/* Cancel matching fsync requests */
hash_seq_init(&hstat, pendingOps);
while ((pfe = (PendingFsyncEntry *) hash_seq_search(&hstat)) != NULL)
{
if (pfe->tag.handler == ftag->handler &&
- syncsw[ftag->handler].sync_filetagmatches(ftag, &pfe->tag))
+ filetagmatches(ftag, &pfe->tag))
pfe->canceled = true;
}
@@ -522,7 +724,7 @@ RememberSyncRequest(const FileTag *ftag, SyncRequestType type)
PendingUnlinkEntry *pue = (PendingUnlinkEntry *) lfirst(cell);
if (pue->tag.handler == ftag->handler &&
- syncsw[ftag->handler].sync_filetagmatches(ftag, &pue->tag))
+ filetagmatches(ftag, &pue->tag))
pue->canceled = true;
}
}
diff --git a/src/include/storage/sync.h b/src/include/storage/sync.h
index 88290500bc9..959a4f72a52 100644
--- a/src/include/storage/sync.h
+++ b/src/include/storage/sync.h
@@ -29,8 +29,13 @@ typedef enum SyncRequestType
} SyncRequestType;
/*
- * Which set of functions to use to handle a given request. The values of
- * the enumerators must match the indexes of the function table in sync.c.
+ * Which set of functions to use to handle a given request. Built-in
+ * handlers occupy the fixed enum values below; extensions register
+ * additional handlers via register_sync_handler() during
+ * shared_preload_libraries initialization and receive IDs starting
+ * at SYNC_HANDLER_FIRST_DYNAMIC. The values of the built-in
+ * enumerators must match the order in which InitSync() pre-registers
+ * the corresponding SyncOps structs in sync.c.
*/
typedef enum SyncRequestHandler
{
@@ -39,9 +44,19 @@ typedef enum SyncRequestHandler
SYNC_HANDLER_COMMIT_TS,
SYNC_HANDLER_MULTIXACT_OFFSET,
SYNC_HANDLER_MULTIXACT_MEMBER,
- SYNC_HANDLER_NONE,
+
+ /* Extensions' dynamic handler IDs start here. */
+ SYNC_HANDLER_FIRST_DYNAMIC,
+
+ /*
+ * Sentinel for "no handler": fits in int16, outside the valid ID range so
+ * it cannot be confused with any registered handler.
+ */
+ SYNC_HANDLER_NONE = -1,
} SyncRequestHandler;
+#define SYNC_HANDLER_MAX INT16_MAX
+
/*
* 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
@@ -55,6 +70,25 @@ typedef struct FileTag
uint64 segno;
} FileTag;
+/*
+ * Dispatch table entry for a sync handler. Public so extensions can
+ * define their own SyncOps and pass them to register_sync_handler().
+ *
+ * sync_syncfiletag is required. sync_unlinkfiletag and
+ * sync_filetagmatches may be NULL if the handler does not support
+ * SYNC_UNLINK_REQUEST or SYNC_FILTER_REQUEST respectively, matching
+ * the pattern of the built-in CLOG/commit_ts/multixact handlers which
+ * only define sync_syncfiletag.
+ */
+typedef struct SyncOps
+{
+ int (*sync_syncfiletag) (const FileTag *ftag, char *path);
+ int (*sync_unlinkfiletag) (const FileTag *ftag, char *path);
+ bool (*sync_filetagmatches) (const FileTag *ftag,
+ const FileTag *candidate);
+} SyncOps;
+
+extern void InitSyncHandlers(void);
extern void InitSync(void);
extern void SyncPreCheckpoint(void);
extern void SyncPostCheckpoint(void);
@@ -63,4 +97,28 @@ extern void RememberSyncRequest(const FileTag *ftag, SyncRequestType type);
extern bool RegisterSyncRequest(const FileTag *ftag, SyncRequestType type,
bool retryOnError);
+/*
+ * Register a custom sync handler. Returns the assigned handler ID
+ * which the extension stores in FileTag.handler when queueing sync
+ * requests via RegisterSyncRequest().
+ *
+ * MUST be called during shared_preload_libraries initialization
+ * (before process_shared_preload_libraries_done is set); later calls
+ * raise FATAL. `name` is used for error messages and is pstrdup'd
+ * into TopMemoryContext by the caller; callers do not need to keep
+ * the buffer alive.
+ *
+ * `ops->sync_syncfiletag` is required; the other two pointers may
+ * be NULL if the handler does not participate in SYNC_UNLINK_REQUEST
+ * or SYNC_FILTER_REQUEST flows.
+ *
+ * The returned ID is stable for the lifetime of the postmaster.
+ * Sync requests live only in the checkpointer's in-memory pendingOps
+ * hash table (they are not persisted across restarts), so there is
+ * no cross-restart stability requirement beyond the same
+ * shared_preload_libraries order that smgr_register() already relies
+ * on.
+ */
+extern int16 register_sync_handler(const SyncOps *ops, const char *name);
+
#endif /* SYNC_H */
--
2.47.3