From 2c21d4d24e9eecdc728d6daabf499927b674a2eb Mon Sep 17 00:00:00 2001 From: Sergey Sargsyan <78570620+Serge-sudo@users.noreply.github.com> Date: Thu, 19 Mar 2026 15:01:04 +0000 Subject: [PATCH] Fix loss of root/meta blkno after self-invalidation; defer invalidation processing under page locks Fix an issue where B-tree descriptor state (`metaPageBlkno` and `rootPageBlkno`) could be lost after processing an invalidation message within the same transaction. Previously, after `o_btree_load_shmem()` initialized the descriptor, a invalidation message could be processed before the descriptor was used. This caused the descriptor to be recreated, resetting `metaPageBlkno` and `rootPageBlkno` to `InvalidBlockNumber`, which led to assertion failures (e.g. in `btree_ctid_get_and_inc`). While investigating, another problem was identified: invalidation message processing may occur while holding page locks. Invalidation handlers access system trees, and if required pages are evicted, they trigger page loads. This leads to `load_page()` being called while locks are held, hitting assertions that forbid holding locks during page load. To address both issues: * Ensure descriptor state is not lost due to mid-operation self-invalidation * Introduce deferred invalidation handling: * Skip invalidation message processing when it is unsafe (e.g. while holding page locks) * Process pending invalidation messages at the next safe point This prevents descriptor corruption and avoids unsafe page loads under locks. --- src/backend/utils/cache/inval.c | 18 ++++++++++++++++++ src/include/utils/inval.h | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c index f5775880416..4fbca7b5711 100644 --- a/src/backend/utils/cache/inval.c +++ b/src/backend/utils/cache/inval.c @@ -240,6 +240,9 @@ static TransInvalidationInfo *transInvalInfo = NULL; /* GUC storage */ int debug_discard_caches = 0; +/* Hook for extensions to receive custom invalidation messages */ +ReceiveCustomInvalMessage_hook_type ReceiveCustomInvalMessage_hook = NULL; + /* * Dynamically-registered callback functions. Current implementation * assumes there won't be enough of these to justify a dynamically resizable @@ -827,6 +830,18 @@ InvalidateSystemCaches(void) InvalidateSystemCachesExtended(false); } +/* + * ReceiveCustomInvalMessage + * Call the ReceiveCustomInvalMessage_hook if set, allowing extensions + * to process their own invalidation messages. + */ +void +ReceiveCustomInvalMessage(void) +{ + if (ReceiveCustomInvalMessage_hook) + (*ReceiveCustomInvalMessage_hook) (); +} + /* * AcceptInvalidationMessages * Read and process invalidation messages from the shared invalidation @@ -838,6 +853,9 @@ InvalidateSystemCaches(void) void AcceptInvalidationMessages(void) { + /* Process any messages from external sources first. */ + ReceiveCustomInvalMessage(); + ReceiveSharedInvalidMessages(LocalExecuteInvalidationMessage, InvalidateSystemCaches); diff --git a/src/include/utils/inval.h b/src/include/utils/inval.h index 69498b9f77f..1ab129f9376 100644 --- a/src/include/utils/inval.h +++ b/src/include/utils/inval.h @@ -20,12 +20,17 @@ extern PGDLLIMPORT int debug_discard_caches; +/* Hook for extensions to receive custom invalidation messages */ +typedef void (*ReceiveCustomInvalMessage_hook_type) (void); +extern PGDLLIMPORT ReceiveCustomInvalMessage_hook_type ReceiveCustomInvalMessage_hook; + typedef void (*SyscacheCallbackFunction) (Datum arg, int cacheid, uint32 hashvalue); typedef void (*RelcacheCallbackFunction) (Datum arg, Oid relid); typedef void (*UsercacheCallbackFunction) (Datum arg, Oid arg1, Oid arg2, Oid arg3); extern void AcceptInvalidationMessages(void); +extern void ReceiveCustomInvalMessage(void); extern void AtEOXact_Inval(bool isCommit);