Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/backend/cluster/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ OBJS = \
cluster_visibility_resolve.o \
cluster_visibility_verdict.o \
cluster_undo_record.o \
cluster_undo_resid.o \
cluster_undo_retention.o \
cluster_undo_srf.o \
cluster_cr.o \
Expand Down
19 changes: 19 additions & 0 deletions src/backend/cluster/cluster_grd.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
#include "cluster/cluster_epoch.h" /* spec-4.6 D1 — accepted epoch reads */
#include "cluster/cluster_reconfig.h" /* spec-4.6 D1 — reconfig event consume */
#include "cluster/cluster_thread_recovery.h" /* spec-4.11 D3 — unfreeze gate */
#include "cluster/cluster_undo_resid.h" /* spec-5.22a D1-5 — undo-class hash-route guard */
#include "storage/procsignal.h" /* spec-4.6 D3 — redeclare broadcast */
#include "storage/sinvaladt.h" /* spec-4.6 D3 — BackendIdGetProc */
#include "utils/timestamp.h" /* spec-4.6 D1 — barrier deadline */
Expand Down Expand Up @@ -993,6 +994,24 @@ cluster_grd_lookup_master(const ClusterResId *resid)
uint32 shard_id;
int32 master;

/*
* PGRAC: spec-5.22a D1-5 -- undo resids are owner-as-master resources;
* their master is the encoded owner (cluster_undo_resid_master), never a
* shard-hash node. A hash-derived master would place the undo authority
* at a node that does not own the undo, so no caller may hash-route the
* undo class. Fail closed: no data-plane path legitimately reaches here
* with an undo resid.
*/
if (resid != NULL && resid->type == CLUSTER_UNDO_RESID_TYPE) {
Assert(false);
ereport(
ERROR,
(errcode(ERRCODE_CLUSTER_UNDO_RESID_HASH_ROUTED),
errmsg("undo resource id must not be routed through the GRD hash master lookup"),
errhint(
"Undo resources are owner-as-master; route via cluster_undo_resid_master().")));
}

Assert(cluster_grd_state != NULL);

shard_id = cluster_grd_shard_for_resource(resid);
Expand Down
134 changes: 134 additions & 0 deletions src/backend/cluster/cluster_undo_resid.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*-------------------------------------------------------------------------
*
* cluster_undo_resid.c
* Shared-undo block resource identity + owner-as-master routing --
* pure layer (spec-5.22a D1).
*
* This file ships the backend-pure layer: the undo resid
* encoder/decoder, the class discriminator, the owner-as-master
* routing function, and the anti-ABA generation predicate. None of
* these touch elog / shmem / locks, so the cluster_unit test links the
* object standalone. The data plane that consumes this identity
* (grant / PI / block serving / recovery materialization / retention)
* lands with later deliverables.
*
*
* Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
* Portions Copyright (c) 2026, pgrac contributors
*
* Author: SqlRush <sqlrush@gmail.com>
*
* IDENTIFICATION
* src/backend/cluster/cluster_undo_resid.c
*
* NOTES
* This is a pgrac-original file (no derivation from PostgreSQL).
* Spec: spec-5.22a-undo-block-resource-identity.md (D1, §2.2 / §3.1)
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"

#include "cluster/cluster_scn.h" /* SCN_NODE_ID_VALID */
#include "cluster/cluster_undo_resid.h"

/*
* cluster_undo_resid_encode -- build the undo-block resource id.
*
* field3 is the segment reuse generation (the segment header wrap_count),
* so a recycled segment lands a distinct resource (ABA defence),
* mirroring the HW relfilenode field. field4 is the owning instance:
* the undo authority lives at the owner, so the owner is part of the
* identity, not a routing afterthought.
*/
void
cluster_undo_resid_encode(int32 owner_node, uint32 undo_segment, uint32 block_no, uint32 generation,
ClusterResId *dst)
{
Assert(dst != NULL);
if (dst == NULL)
return;
Assert(SCN_NODE_ID_VALID(owner_node));

dst->field1 = undo_segment;
dst->field2 = block_no;
dst->field3 = generation;
dst->field4 = (uint16)owner_node;
dst->type = CLUSTER_UNDO_RESID_TYPE;
dst->lockmethodid = DEFAULT_LOCKMETHOD;
}

/*
* cluster_undo_resid_decode -- split an undo resid back into its fields.
*
* The caller must pass an undo-class resid (Assert enforced); the
* decoder is the wire-ABI boundary, so it never guesses at foreign
* classes.
*/
void
cluster_undo_resid_decode(const ClusterResId *rid, int32 *owner_node, uint32 *undo_segment,
uint32 *block_no, uint32 *generation)
{
Assert(rid != NULL);
if (rid == NULL || owner_node == NULL || undo_segment == NULL || block_no == NULL
|| generation == NULL)
return;
Assert(rid->type == CLUSTER_UNDO_RESID_TYPE);

*owner_node = (int32)rid->field4;
*undo_segment = rid->field1;
*block_no = rid->field2;
*generation = rid->field3;
}

/*
* cluster_undo_resid_is_undo -- class discriminator.
*/
bool
cluster_undo_resid_is_undo(const ClusterResId *rid)
{
Assert(rid != NULL);
if (rid == NULL)
return false;

return rid->type == CLUSTER_UNDO_RESID_TYPE;
}

/*
* cluster_undo_resid_master -- owner-as-master routing.
*
* Returns the encoded owner_node directly: the undo authority lives at
* the owning instance, so the master is part of the identity and is
* NEVER derived from a shard hash. A hash-derived master would place
* the authority at a node that does not own the undo, which is exactly
* the misrouting the GRD-side guard fails closed on.
*/
int32
cluster_undo_resid_master(const ClusterResId *rid)
{
Assert(rid != NULL);
if (rid == NULL)
return -1;
Assert(rid->type == CLUSTER_UNDO_RESID_TYPE);

return (int32)rid->field4;
}

/*
* cluster_undo_resid_generation_matches -- anti-ABA check.
*
* false means the reference predates a whole-segment recycle (the
* segment header wrap_count moved on) and the caller MUST fail closed;
* it must never be treated as a match.
*/
bool
cluster_undo_resid_generation_matches(const ClusterResId *rid, uint32 expected_generation)
{
Assert(rid != NULL);
if (rid == NULL)
return false;
Assert(rid->type == CLUSTER_UNDO_RESID_TYPE);

return rid->field3 == expected_generation;
}
9 changes: 9 additions & 0 deletions src/backend/utils/errcodes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,15 @@ Section: Class 53 - Insufficient Resources (pgrac extension)
# retried; never write dependent bytes to storage.
53R9P E ERRCODE_CLUSTER_SMART_FUSION_DEP_LOST cluster_smart_fusion_dep_lost

# spec-5.22a D1: undo resids are owner-as-master resources (the resource
# master IS the owning instance); routing one through the GRD shard-hash
# master lookup would place the authority at a node that does not own the
# undo. No caller may hash-route an undo resid; the hash lookup fails
# closed here (route via cluster_undo_resid_master instead). Q is the next
# free slot in the 53R9 family (A..P allocated; X taken by the clean-page
# X-transfer band below).
53R9Q E ERRCODE_CLUSTER_UNDO_RESID_HASH_ROUTED cluster_undo_resid_hash_routed

# spec-5.2a D6: clean-page X-transfer enabler terminal fail-closed. A clean
# (sequence) page X-transfer could not complete safely and there is no proven-
# safe fallback: a 3-node third-party master clean transfer (out of the 2-node
Expand Down
149 changes: 149 additions & 0 deletions src/include/cluster/cluster_undo_resid.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*-------------------------------------------------------------------------
*
* cluster_undo_resid.h
* Shared-undo block resource identity + owner-as-master routing
* contract -- spec-5.22a D1.
*
* Names an undo block (including the segment TT header block) as a
* first-class cluster resource: (owner_node, undo_segment, block_no,
* generation) encoded into the 16-byte ClusterResId wire format. Undo
* is the first owner-as-master resid class: the resource master IS the
* owning instance (cluster_undo_resid_master returns owner_node), never
* a GRD shard-hash master. Hash-routing an undo resid through
* cluster_grd_lookup_master / cluster_gcs_lookup_master is a fail-closed
* error: the undo authority lives at the owner and a hash-derived master
* would bypass it.
*
* generation carries the segment reuse generation (the segment header
* wrap_count): a recycled segment reuses (undo_segment, block_no) for
* different content, so a generation mismatch means a stale reference
* and the caller must fail closed. The owner membership epoch (owner
* incarnation) is deliberately NOT part of the tag: it is a grant-time
* ownership-validity attribute negotiated on the authority path, not a
* static identity field.
*
* This header declares the PURE layer only (no elog / shmem / lock;
* standalone-linkable for cluster_unit). The data plane that consumes
* this identity (grant / PI / block serving / recovery materialization /
* retention) lands with later deliverables.
*
*
* Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
* Portions Copyright (c) 2026, pgrac contributors
*
* Author: SqlRush <sqlrush@gmail.com>
*
* IDENTIFICATION
* src/include/cluster/cluster_undo_resid.h
*
* NOTES
* This is a pgrac-original file (no derivation from PostgreSQL).
* Spec: spec-5.22a-undo-block-resource-identity.md (D1, §2.2 / §3.1)
*
*-------------------------------------------------------------------------
*/
#ifndef CLUSTER_UNDO_RESID_H
#define CLUSTER_UNDO_RESID_H

#include "cluster/cluster_cf_enqueue.h" /* CLUSTER_CF_RESID_TYPE (collision check) */
#include "cluster/cluster_dl.h" /* CLUSTER_DL_RESID_TYPE (collision check) */
#include "cluster/cluster_grd.h" /* ClusterResId */
#include "cluster/cluster_hw.h" /* CLUSTER_HW_RESID_TYPE (collision check) */
#include "cluster/cluster_ir.h" /* CLUSTER_IR_RESID_TYPE (collision check) */
#include "cluster/cluster_ko.h" /* CLUSTER_KO_RESID_TYPE (collision check) */
#include "cluster/cluster_oid_lease.h" /* CLUSTER_OID_RESID_TYPE (collision check) */
#include "cluster/cluster_relmap_lock.h" /* CLUSTER_RELMAP_RESID_TYPE (collision check) */
#include "cluster/cluster_sequence.h" /* CLUSTER_SQ_RESID_TYPE (collision check) */
#include "cluster/cluster_ts.h" /* CLUSTER_TT_RESID_TYPE (collision check) */
#include "storage/lock.h" /* LOCKTAG_LAST_TYPE, DEFAULT_LOCKMETHOD */

/*
* CLUSTER_UNDO_RESID_TYPE -- undo-block resource-id namespace marker.
* 0xF9 is the next free slot after the contiguous 0xF0-0xF8 run. Must be
* above every PG LockTagType and distinct from every existing resid class.
*
* NB: 0xF3 is double-booked today by CLUSTER_DL_RESID_TYPE (cluster_dl.h)
* and the backend-local CLUSTER_RAW_LAYOUT_RESID_TYPE
* (cluster_shared_fs_block_device.c), which this cross-assert net cannot
* name. 0xF9 collides with neither; cleaning up the 0xF3 double-booking
* is a registered follow-up outside this header.
*/
#define CLUSTER_UNDO_RESID_TYPE 0xF9

StaticAssertDecl(CLUSTER_UNDO_RESID_TYPE > LOCKTAG_LAST_TYPE,
"undo resid namespace must not collide with any PG LockTagType");
StaticAssertDecl(CLUSTER_UNDO_RESID_TYPE != CLUSTER_SQ_RESID_TYPE,
"undo and SQ resid namespaces must be distinct");
StaticAssertDecl(CLUSTER_UNDO_RESID_TYPE != CLUSTER_CF_RESID_TYPE,
"undo and CF resid namespaces must be distinct");
StaticAssertDecl(CLUSTER_UNDO_RESID_TYPE != CLUSTER_HW_RESID_TYPE,
"undo and HW resid namespaces must be distinct");
StaticAssertDecl(CLUSTER_UNDO_RESID_TYPE != CLUSTER_DL_RESID_TYPE,
"undo and DL resid namespaces must be distinct");
StaticAssertDecl(CLUSTER_UNDO_RESID_TYPE != CLUSTER_TT_RESID_TYPE,
"undo and TT (tablespace-DDL) resid namespaces must be distinct");
StaticAssertDecl(CLUSTER_UNDO_RESID_TYPE != CLUSTER_IR_RESID_TYPE,
"undo and IR resid namespaces must be distinct");
StaticAssertDecl(CLUSTER_UNDO_RESID_TYPE != CLUSTER_KO_RESID_TYPE,
"undo and KO resid namespaces must be distinct");
StaticAssertDecl(CLUSTER_UNDO_RESID_TYPE != CLUSTER_OID_RESID_TYPE,
"undo and OID-lease resid namespaces must be distinct");
StaticAssertDecl(CLUSTER_UNDO_RESID_TYPE != CLUSTER_RELMAP_RESID_TYPE,
"undo and RELMAP resid namespaces must be distinct");

/*
* ClusterResId field mapping for the undo class (16 bytes, NOT memcpy --
* cluster_undo_resid_encode/decode are the wire-ABI boundary):
*
* field1 = undo_segment (per-instance undo segment number)
* field2 = block_no (block number within the segment; block 0
* is the segment TT header block -- DATA and
* TT blocks share this one class)
* field3 = generation (segment reuse generation == the segment
* header wrap_count; anti-ABA guard against
* whole-segment recycling)
* field4 = owner_node (owning instance node id; uint16 on the
* wire, valid range [0, SCN_MAX_VALID_NODE_ID])
* type = CLUSTER_UNDO_RESID_TYPE
* lockmethodid = DEFAULT_LOCKMETHOD
*
* The owner membership epoch (owner incarnation) is NOT in the tag; it is
* negotiated at grant/serve time on the authority path.
*/

/*
* cluster_undo_resid_encode -- build the undo-block resource id.
*/
extern void cluster_undo_resid_encode(int32 owner_node, uint32 undo_segment, uint32 block_no,
uint32 generation, ClusterResId *dst);

/*
* cluster_undo_resid_decode -- split an undo resid back into its fields.
* Must only be called on a resid whose type is CLUSTER_UNDO_RESID_TYPE.
*/
extern void cluster_undo_resid_decode(const ClusterResId *rid, int32 *owner_node,
uint32 *undo_segment, uint32 *block_no, uint32 *generation);

/*
* cluster_undo_resid_is_undo -- class discriminator (type == 0xF9).
*/
extern bool cluster_undo_resid_is_undo(const ClusterResId *rid);

/*
* cluster_undo_resid_master -- owner-as-master routing: returns the
* encoded owner_node directly, NEVER a hash-derived master. Undo
* resources route exclusively through this function; the GRD/GCS
* hash-master lookups reject the undo class (fail closed).
*/
extern int32 cluster_undo_resid_master(const ClusterResId *rid);

/*
* cluster_undo_resid_generation_matches -- anti-ABA check: false means the
* reference is stale (the segment was recycled) and the caller MUST fail
* closed; it must never be treated as a match.
*/
extern bool cluster_undo_resid_generation_matches(const ClusterResId *rid,
uint32 expected_generation);

#endif /* CLUSTER_UNDO_RESID_H */
2 changes: 2 additions & 0 deletions src/test/cluster_tap/t/006_errcodes.pl
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ sub raise_unknown
"cluster_reconfig_in_progress -> 53R60");
is(raise_and_get_sqlstate('cluster_backup_incomplete'), '53RAD',
"cluster_backup_incomplete -> 53RAD");
is(raise_and_get_sqlstate('cluster_undo_resid_hash_routed'), '53R9Q',
"cluster_undo_resid_hash_routed -> 53R9Q");
is(raise_and_get_sqlstate('cluster_adg_apply_lag_excessive'), '57R06',
"cluster_adg_apply_lag_excessive -> 57R06");
is(raise_and_get_sqlstate('cluster_adg_standby_unresolvable'), '57R07',
Expand Down
Loading
Loading