Skip to content
Merged
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
9 changes: 8 additions & 1 deletion src/backend/cluster/cluster_debug.c
Original file line number Diff line number Diff line change
Expand Up @@ -2167,7 +2167,12 @@ dump_gcs(ReturnSetInfo *rsinfo)
* dispatch pump never waits on a foreign pin); park_expired /
* park_overflow = directives the master's ack budget fail-closes;
* drop_pinned_deny = grant/transfer drops refused with a retryable
* deny because a foreign pin held the copy. */
* deny because a foreign pin held the copy. xfer_stale_deny (round-6)
* = a grant/transfer drop refused because a local writer committed to
* the page between the ship-image copy and the drop (page LSN advanced
* past copy-time = generation gate); the re-serve ships the current
* page — a non-zero delta proves the copy->drop window was exercised
* and closed instead of silently losing the write. */
emit_row(rsinfo, "gcs", "invalidate_parked_count",
fmt_int64((int64)cluster_gcs_get_invalidate_parked_count()));
emit_row(rsinfo, "gcs", "invalidate_park_expired_count",
Expand All @@ -2176,6 +2181,8 @@ dump_gcs(ReturnSetInfo *rsinfo)
fmt_int64((int64)cluster_gcs_get_invalidate_park_overflow_count()));
emit_row(rsinfo, "gcs", "drop_pinned_deny_count",
fmt_int64((int64)cluster_gcs_get_drop_pinned_deny_count()));
emit_row(rsinfo, "gcs", "xfer_stale_deny_count",
fmt_int64((int64)cluster_gcs_get_xfer_stale_deny_count()));

/* PGRAC: GCS-race round-4c FUNC-1 — storage-fallback SCN verify rows:
* local pre-read proven current (no I/O) / stale pre-read re-read from
Expand Down
144 changes: 112 additions & 32 deletions src/backend/cluster/cluster_gcs_block.c
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,13 @@ typedef struct ClusterGcsBlockShared {
pg_atomic_uint64 invalidate_park_expired_count;
pg_atomic_uint64 invalidate_park_overflow_count;
pg_atomic_uint64 drop_pinned_deny_count;
/* PGRAC: GCS serve-stall round-6 — the generation gate refused a drop
* because a local writer committed to the page between the ship-image
* copy and the drop (page LSN advanced past copy-time); the serve
* fail-closes with a retryable deny so the re-serve ships the current
* page. A non-zero delta over a workload proves the copy->drop window
* was actually exercised and closed (the silent-lost-write guard). */
pg_atomic_uint64 xfer_stale_deny_count;
/* PGRAC: GCS-race round-4c FUNC-1 — storage-fallback SCN verify/refresh.
* A state=N GRANTED_STORAGE_FALLBACK now carries the master's
* pi_watermark_scn (reply page_lsn field reused as an SCN carrier); the
Expand Down Expand Up @@ -497,6 +504,7 @@ cluster_gcs_block_shmem_init(void)
pg_atomic_init_u64(&ClusterGcsBlock->invalidate_park_expired_count, 0);
pg_atomic_init_u64(&ClusterGcsBlock->invalidate_park_overflow_count, 0);
pg_atomic_init_u64(&ClusterGcsBlock->drop_pinned_deny_count, 0);
pg_atomic_init_u64(&ClusterGcsBlock->xfer_stale_deny_count, 0);
pg_atomic_init_u64(&ClusterGcsBlock->fallback_scn_verify_pass_count, 0);
pg_atomic_init_u64(&ClusterGcsBlock->fallback_scn_refresh_count, 0);
pg_atomic_init_u64(&ClusterGcsBlock->fallback_scn_failclosed_count, 0);
Expand Down Expand Up @@ -3637,6 +3645,30 @@ cluster_gcs_local_master_x_transfer_and_wait(BufferDesc *buf, int32 holder_node,
* never a silent stale grant (Rule 8.A). */
if (clean_eligible)
pg_atomic_fetch_add_u64(&ClusterGcsBlock->clean_page_xfer_fail_closed_count, 1);

/*
* PGRAC: GCS serve-stall round-6 — a transient revoke deny (the holder
* fail-closed the drop with DENIED_MASTER_NOT_HOLDER / DENIED_PENDING_X
* because the copy was pinned (round-5 A2) or a local writer committed
* inside the copy->drop window (round-6 generation gate)) is a RETRYABLE
* condition: the re-serve ships the current page once the pin clears / the
* window is done. Surface the retryable class-53 code (53R9X) so an
* application driver retries the statement, instead of FEATURE_NOT_SUPPORTED
* (0A000), which reads as "permanently unsupported" and is never retried.
* A genuine timeout (got_reply == false) keeps 0A000: we could not prove
* the holder's state, so it is not a bounded transient.
*/
if (got_reply
&& (reply_status == (uint8)GCS_BLOCK_REPLY_DENIED_MASTER_NOT_HOLDER
|| reply_status == (uint8)GCS_BLOCK_REPLY_DENIED_PENDING_X))
ereport(ERROR, (errcode(ERRCODE_CLUSTER_CLEAN_PAGE_XFER_UNAVAILABLE),
errmsg("cluster_gcs_block: X holder %d transiently refused the transfer "
"for tag spc=%u db=%u relNumber=%u block=%u",
holder_node, tag.spcOid, tag.dbOid,
(unsigned int)BufTagGetRelNumber(&tag), (unsigned int)tag.blockNum),
errhint("The holder's copy was pinned, or a local writer committed during "
"the transfer; retry the transaction.")));

ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cluster_gcs_block: could not obtain X transfer from X holder %d "
"for tag spc=%u db=%u relNumber=%u block=%u",
Expand Down Expand Up @@ -4472,14 +4504,33 @@ cluster_gcs_handle_block_request_envelope(const ClusterICEnvelope *env, const vo
* deny contract is "back off and retry, the retry
* re-evaluates".
*/
if (cluster_bufmgr_drop_block_for_gcs_no_wire(req->tag, &drop_lsn)
== CLUSTER_BUFMGR_GCS_DROP_PINNED) {
pg_atomic_fetch_add_u64(&ClusterGcsBlock->drop_pinned_deny_count, 1);
cluster_gcs_block_dedup_remove(dedup_worker_id, &key);
gcs_block_send_reply(req->sender_node, req,
GCS_BLOCK_REPLY_DENIED_PENDING_X, InvalidXLogRecPtr,
NULL);
return;
/* GCS serve-stall round-6 RED harness: hold the copy->drop
* window open (see cluster_inject.c registry note). */
CLUSTER_INJECTION_POINT("cluster-gcs-xfer-copy-drop-window");
{
/* GCS serve-stall round-6: pass the copy-time page_lsn
* (captured by get_ship_image above) as the generation
* token. PINNED (a live foreign pin) and STALE (a local
* writer committed since the copy) both mean the shipped
* image must NOT be granted — fail-closed with the same
* retryable deny so the requester re-asks and the re-serve
* copies the current page (Rule 8.A). */
ClusterBufmgrGcsDropResult dres = cluster_bufmgr_drop_block_for_gcs_no_wire(
req->tag, page_lsn, &drop_lsn);

if (dres == CLUSTER_BUFMGR_GCS_DROP_PINNED
|| dres == CLUSTER_BUFMGR_GCS_DROP_STALE) {
if (dres == CLUSTER_BUFMGR_GCS_DROP_STALE)
pg_atomic_fetch_add_u64(&ClusterGcsBlock->xfer_stale_deny_count, 1);
else
pg_atomic_fetch_add_u64(&ClusterGcsBlock->drop_pinned_deny_count,
1);
cluster_gcs_block_dedup_remove(dedup_worker_id, &key);
gcs_block_send_reply(req->sender_node, req,
GCS_BLOCK_REPLY_DENIED_PENDING_X,
InvalidXLogRecPtr, NULL);
return;
}
}
/* PGRAC: spec-6.12h D-h3a — ordering pin: the PI
* conversion (inside the drop above) samples its
Expand Down Expand Up @@ -6404,29 +6455,48 @@ cluster_gcs_handle_block_forward_envelope(const ClusterICEnvelope *env, const vo
* keep our X untouched. NOT_RESIDENT still grants: no
* copy left to stale-flush.
*/
if (cluster_bufmgr_drop_block_for_gcs_no_wire(fwd->tag, &drop_lsn)
== CLUSTER_BUFMGR_GCS_DROP_PINNED) {
pg_atomic_fetch_add_u64(&ClusterGcsBlock->drop_pinned_deny_count, 1);
/* undo the from-holder ship count taken with the
* grant status above — this reply is a deny now */
pg_atomic_fetch_sub_u64(&ClusterGcsBlock->block_from_holder_ship_count, 1);
hdr->status = (uint8)GCS_BLOCK_REPLY_DENIED_MASTER_NOT_HOLDER;
} else {
/* PGRAC: spec-6.12h D-h3a — ordering pin: the PI
* conversion (inside the drop above) precedes the reply
* send at the bottom of this handler
* (cluster_ic_rdma_send_envelope_sge), so the requester
* observes an envelope stamped at-or-above the ship-SCN
* boundary (cluster_pi_shadow.h proof item 2). */
pg_atomic_fetch_add_u64(&ClusterGcsBlock->block_x_transfer_ship_count, 1);
if (GcsBlockForwardPayloadIsCleanEligible(fwd))
pg_atomic_fetch_add_u64(&ClusterGcsBlock->clean_page_xfer_count, 1);
/* PGRAC: spec-6.12h D-h2 — if the D-h1 conversion kept our
* outgoing copy as a Past Image, report it to the master
* (unsolicited PI_KEPT ride; fire-and-forget — a lost note
* only leaves the PI untracked, fail-safe lingering). */
if (cluster_bufmgr_block_is_pi(fwd->tag))
gcs_block_pi_kept_note_send(fwd->tag, fwd->master_node);
/* GCS serve-stall round-6 RED harness: hold the copy->drop
* window open (see cluster_inject.c registry note). */
CLUSTER_INJECTION_POINT("cluster-gcs-xfer-copy-drop-window");
/* GCS serve-stall round-6: page_lsn (copy-time, from
* get_ship_image above) is the generation token. STALE (a
* local writer committed since the copy) joins PINNED as a
* retryable deny — never ship a stale image over a committed
* write (Rule 8.A). */
{
ClusterBufmgrGcsDropResult dres = cluster_bufmgr_drop_block_for_gcs_no_wire(
fwd->tag, page_lsn, &drop_lsn);

if (dres == CLUSTER_BUFMGR_GCS_DROP_PINNED
|| dres == CLUSTER_BUFMGR_GCS_DROP_STALE) {
if (dres == CLUSTER_BUFMGR_GCS_DROP_STALE)
pg_atomic_fetch_add_u64(&ClusterGcsBlock->xfer_stale_deny_count, 1);
else
pg_atomic_fetch_add_u64(&ClusterGcsBlock->drop_pinned_deny_count,
1);
/* undo the from-holder ship count taken with the
* grant status above — this reply is a deny now */
pg_atomic_fetch_sub_u64(&ClusterGcsBlock->block_from_holder_ship_count,
1);
hdr->status = (uint8)GCS_BLOCK_REPLY_DENIED_MASTER_NOT_HOLDER;
} else {
/* PGRAC: spec-6.12h D-h3a — ordering pin: the PI
* conversion (inside the drop above) precedes the reply
* send at the bottom of this handler
* (cluster_ic_rdma_send_envelope_sge), so the requester
* observes an envelope stamped at-or-above the ship-SCN
* boundary (cluster_pi_shadow.h proof item 2). */
pg_atomic_fetch_add_u64(&ClusterGcsBlock->block_x_transfer_ship_count,
1);
if (GcsBlockForwardPayloadIsCleanEligible(fwd))
pg_atomic_fetch_add_u64(&ClusterGcsBlock->clean_page_xfer_count, 1);
/* PGRAC: spec-6.12h D-h2 — if the D-h1 conversion kept our
* outgoing copy as a Past Image, report it to the master
* (unsolicited PI_KEPT ride; fire-and-forget — a lost note
* only leaves the PI untracked, fail-safe lingering). */
if (cluster_bufmgr_block_is_pi(fwd->tag))
gcs_block_pi_kept_note_send(fwd->tag, fwd->master_node);
}
}
}
}
Expand Down Expand Up @@ -7313,8 +7383,12 @@ gcs_block_invalidate_execute(const GcsBlockInvalidatePayload *inv)
ack_status = 2; /* race: not resident */
break;
case CLUSTER_BUFMGR_GCS_DROP_PINNED:
case CLUSTER_BUFMGR_GCS_DROP_STALE:
/* GCS serve-stall round-5 (A2): nothing changed, no ACK — the
* caller parks the directive and the LMS loop retries it. */
* caller parks the directive and the LMS loop retries it. STALE is
* unreachable here (the invalidate wrapper passes no expected_lsn, so
* its generation gate never fires); treated like PINNED defensively
* (round-6). */
return false;
}

Expand Down Expand Up @@ -8048,6 +8122,12 @@ cluster_gcs_get_drop_pinned_deny_count(void)
return ClusterGcsBlock ? pg_atomic_read_u64(&ClusterGcsBlock->drop_pinned_deny_count) : 0;
}

uint64
cluster_gcs_get_xfer_stale_deny_count(void)
{
return ClusterGcsBlock ? pg_atomic_read_u64(&ClusterGcsBlock->xfer_stale_deny_count) : 0;
}

/* PGRAC: GCS-race round-4c FUNC-1 — 3 storage-fallback verify accessors. */
uint64
cluster_gcs_get_fallback_scn_verify_pass_count(void)
Expand Down
14 changes: 14 additions & 0 deletions src/backend/cluster/cluster_inject.c
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,20 @@ static ClusterInjectPoint cluster_injection_points[] = {
{ .name = "cluster-recovery-anchor-force-failclosed" },
{ .name = "cluster-relmap-crash-after-stage" },
{ .name = "cluster-relmap-crash-before-publish" },
/*
* GCS serve-stall round-6 — X-transfer copy->drop generation RED.
*
* cluster-gcs-xfer-copy-drop-window:
* Fires in BOTH destructive X-transfer branches (master==holder
* self-ship and holder-forward), AFTER the ship image was captured
* and BEFORE the local drop. SLEEP holds the copy->drop window
* open so a cached-X local writer can be admitted inside it (the
* write lands after the copy, the drop then discards it): the
* silent-lost-write interleave the generation gate closes (the
* drop's page-LSN check refuses the stale-image drop, retryable).
* No lock/pin is held across the point. Driven by t/393.
*/
{ .name = "cluster-gcs-xfer-copy-drop-window" },
/*
* spec-2.41 D5 / P1-C — SCN lost-write detector behavioral trigger.
*
Expand Down
53 changes: 50 additions & 3 deletions src/backend/storage/buffer/bufmgr.c
Original file line number Diff line number Diff line change
Expand Up @@ -7717,7 +7717,14 @@ cluster_bufmgr_invalidate_block_for_gcs(BufferTag tag, PcmLockMode expected_mode
if (!InvalidateBufferTry(buf)) /* releases the header spinlock */
{
buf_state = LockBufHdr(buf);
if (BufferTagsEqual(&buf->tag, &tag))
/* PGRAC: GCS serve-stall round-6 (gap (c)) — restore the staged N only
* when it is still staged. The header spinlock was dropped for the
* InvalidateBufferTry attempt, so a concurrent grant / transition could
* have moved pcm_state off the staged N; an unconditional write-back
* would clobber that newer authoritative state with our stale saved
* value (Rule 8.A stale-grant). Restore only if nobody else changed it. */
if (BufferTagsEqual(&buf->tag, &tag) &&
buf->pcm_state == (uint8) PCM_STATE_N)
buf->pcm_state = saved_pcm_state;
UnlockBufHdr(buf, buf_state);
return CLUSTER_BUFMGR_GCS_DROP_PINNED;
Expand Down Expand Up @@ -7945,7 +7952,8 @@ cluster_bufmgr_block_pcm_state(BufferTag tag)
* no-stale-flush.
* ======================================================================== */
ClusterBufmgrGcsDropResult
cluster_bufmgr_drop_block_for_gcs_no_wire(BufferTag tag, XLogRecPtr *out_page_lsn)
cluster_bufmgr_drop_block_for_gcs_no_wire(BufferTag tag, XLogRecPtr expected_lsn,
XLogRecPtr *out_page_lsn)
{
uint32 hashcode;
LWLock *partition_lock;
Expand Down Expand Up @@ -7998,6 +8006,26 @@ cluster_bufmgr_drop_block_for_gcs_no_wire(BufferTag tag, XLogRecPtr *out_page_ls
page_lsn = PageGetLSN(page);
}

/*
* PGRAC: GCS serve-stall round-6 — generation gate. A local writer that
* held a cached X grant could commit to this page in the window between
* the transfer's ship-image copy and this drop (gaps (a) cached-X
* no-reverify + (b) copy->drop admission). Its pin is already gone (the
* refcount check above passed), so PINNED cannot catch it; the page LSN,
* read here under the same header spinlock that the writer's unpin
* released, is the generation token. A LSN past the caller's copy-time
* expected_lsn means the captured ship image is STALE: refuse the drop so
* the caller fail-closes with a retryable deny and the re-serve copies the
* current page (Rule 8.A — never grant a stale image over a committed
* write). expected_lsn == Invalid skips the gate.
*/
if (!XLogRecPtrIsInvalid(expected_lsn) && page_lsn != expected_lsn)
{
UnlockBufHdr(buf, buf_state);
LWLockRelease(partition_lock);
return CLUSTER_BUFMGR_GCS_DROP_STALE;
}

UnlockBufHdr(buf, buf_state);
LWLockRelease(partition_lock);

Expand Down Expand Up @@ -8038,6 +8066,18 @@ cluster_bufmgr_drop_block_for_gcs_no_wire(BufferTag tag, XLogRecPtr *out_page_ls
return CLUSTER_BUFMGR_GCS_DROP_PINNED;
}

/* PGRAC: GCS serve-stall round-6 — re-check the generation gate under
* this second header-lock hold: a local writer could have committed
* during the (blocking) XLogFlush window above. A LSN past expected_lsn
* means the ship image is stale; refuse without touching pcm_state
* (Rule 8.A). */
if (!XLogRecPtrIsInvalid(expected_lsn) &&
PageGetLSN((Page) BufHdrGetBlock(buf)) != expected_lsn)
{
UnlockBufHdr(buf, buf_state);
return CLUSTER_BUFMGR_GCS_DROP_STALE;
}

saved_pcm_state = buf->pcm_state;
buf->pcm_state = (uint8) PCM_STATE_N;

Expand All @@ -8051,7 +8091,14 @@ cluster_bufmgr_drop_block_for_gcs_no_wire(BufferTag tag, XLogRecPtr *out_page_ls
if (!InvalidateBufferTry(buf)) /* releases the header spinlock */
{
buf_state = LockBufHdr(buf);
if (BufferTagsEqual(&buf->tag, &tag))
/* PGRAC: GCS serve-stall round-6 (gap (c)) — restore the staged N only
* when it is still staged. The header spinlock was dropped for the
* InvalidateBufferTry attempt, so a concurrent grant / transition could
* have moved pcm_state off the staged N; an unconditional write-back
* would clobber that newer authoritative state with our stale saved
* value (Rule 8.A stale-grant). Restore only if nobody else changed it. */
if (BufferTagsEqual(&buf->tag, &tag) &&
buf->pcm_state == (uint8) PCM_STATE_N)
buf->pcm_state = saved_pcm_state;
UnlockBufHdr(buf, buf_state);
return CLUSTER_BUFMGR_GCS_DROP_PINNED;
Expand Down
14 changes: 13 additions & 1 deletion src/include/cluster/cluster_gcs_block.h
Original file line number Diff line number Diff line change
Expand Up @@ -1858,11 +1858,21 @@ extern void cluster_bufmgr_unlock_resident_stamp(Buffer buffer);
* (no state cleared, no flush relied upon). The caller
* parks the job and retries, or fail-closes with a
* retryable deny — never spins.
* STALE GCS serve-stall round-6: a local writer committed to
* the page between the ship-image copy and this drop (the
* page LSN advanced past the caller's expected copy-time
* LSN), so the already-captured ship image is stale.
* NOTHING was changed; the caller MUST NOT grant the
* stale image — it fail-closes with a retryable deny so
* the re-serve copies the current page (the generation
* binding that closes the copy->drop silent-lost-write
* window, gaps (a)+(b)).
*/
typedef enum ClusterBufmgrGcsDropResult {
CLUSTER_BUFMGR_GCS_DROP_DROPPED = 0,
CLUSTER_BUFMGR_GCS_DROP_NOT_RESIDENT,
CLUSTER_BUFMGR_GCS_DROP_PINNED,
CLUSTER_BUFMGR_GCS_DROP_STALE,
} ClusterBufmgrGcsDropResult;

extern ClusterBufmgrGcsDropResult cluster_bufmgr_invalidate_block_for_gcs(BufferTag tag,
Expand Down Expand Up @@ -1890,7 +1900,8 @@ extern void cluster_gcs_block_fallback_verify_refresh(BufferDesc *buf, BufferTag
* the §3.5 IC-dispatch (LMON) context. XLogFlush+InvalidateBuffer, with the
* cache-eviction release wire suppressed (clears pcm_state=N first). */
extern ClusterBufmgrGcsDropResult
cluster_bufmgr_drop_block_for_gcs_no_wire(BufferTag tag, XLogRecPtr *out_page_lsn);
cluster_bufmgr_drop_block_for_gcs_no_wire(BufferTag tag, XLogRecPtr expected_lsn,
XLogRecPtr *out_page_lsn);

/* PGRAC: spec-6.12h D-h2 — Past Image discard helpers.
* block_is_pi: does this tag's resident buffer hold a D-h1 Past Image
Expand Down Expand Up @@ -2178,6 +2189,7 @@ extern uint64 cluster_gcs_get_invalidate_parked_count(void);
extern uint64 cluster_gcs_get_invalidate_park_expired_count(void);
extern uint64 cluster_gcs_get_invalidate_park_overflow_count(void);
extern uint64 cluster_gcs_get_drop_pinned_deny_count(void);
extern uint64 cluster_gcs_get_xfer_stale_deny_count(void);

/* ============================================================
* Observability accessors (dump_gcs +8 NEW rows for block plane).
Expand Down
Loading
Loading