Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
6abbf79
feat(cluster): record the PCM-X fail-closed arm per node
Jul 18, 2026
9e6d8b6
test(cluster): sync pinned pcm dump counts for the fail-closed site key
Jul 18, 2026
c2b2b41
fix(cluster): capture the internal convert.c fail-closed arms too
Jul 18, 2026
ad47d84
style(cluster): clang-format the fail-closed observability files
Jul 18, 2026
c0955a9
test(cluster): t/400 mid-leg probe of writer wait events and queue ga…
Jul 18, 2026
69f63a9
test(cluster): two-sample mid-leg probe to fingerprint the stall loop
Jul 18, 2026
776af9d
test(cluster): mid-leg probe adds aux-process waits and per-peer wire…
Jul 18, 2026
9eec493
test(cluster): include lmd and interconnect listener in the aux-wait …
Jul 18, 2026
5d686d2
feat(cluster): per-slot master tag/ticket diagnostic rows in the pcm …
Jul 18, 2026
194f3f7
fix(cluster): coherent slot dump + requester escape-arm evidence
Jul 18, 2026
63f1cb2
style(cluster): undo accidental clang-format of PG core bufmgr.c
Jul 18, 2026
10cd1f2
fix(cluster): forward-declare pcm_x_domain_slot for the early dump it…
Jul 18, 2026
2daddda
feat(cluster): local-tag dump, terminal-kick note, fail-closed exit l…
Jul 18, 2026
623e247
fix(cluster): retire cross-lane holder terminal under newer writer RE…
Jul 18, 2026
3a3b97b
test(cluster): rebuild cross-lane retire test on production paths; ti…
Jul 19, 2026
efb2ec5
fix(cluster): wait out transient BUSY at the PCM-X remote-reservation…
Jul 19, 2026
ecfcd41
feat(cluster): absorb interleaved-revoke base drift via one-shot INST…
Jul 19, 2026
03ada81
fix(cluster): unwind nested-guard BARRIER_CLOSED to the heap caller i…
Jul 19, 2026
fbd7f8b
fix(cluster): inherit the published effective grant base into every s…
Jul 19, 2026
e5c7345
fix(cluster): sample the PCM-X capability family atomically and enfor…
Jul 19, 2026
e39bca7
test(cluster): execute the preflight BUSY wait arm over the real owne…
Jul 19, 2026
e6134eb
style(cluster): clang-format the review-fix surfaces
Jul 19, 2026
776fdb8
fix(cluster): admit the self-loopback V2 INSTALL_READY at ingress
Jul 19, 2026
cb7ab02
fix(heap): make every barrier-re-entered update/delete decision pass-…
Jul 19, 2026
d7cac6a
fix(cluster): wait out a live ownership lifecycle at the legacy grant…
Jul 19, 2026
832019b
fix(cluster): hand the holder-gate BARRIER_CLOSED refusal to the barr…
Jul 19, 2026
f042473
fix(cluster): log the refused self-source DRAIN proof before fusing t…
Jul 19, 2026
440e963
fix(cluster): count master leg re-arms and log a repeating drive stall
Jul 19, 2026
6d20a13
fix(cluster): log the live descriptor lifecycle behind a repeating RE…
Jul 19, 2026
089d3ce
fix(cluster): walk every terminal ticket per retry tick, oldest first
Jul 19, 2026
c46f514
fix(cluster): release the self-source DRAIN record on the exact compl…
Jul 19, 2026
26bf334
fix(cluster): name the retire-collect arm before its shared fail-clos…
Jul 19, 2026
1be1998
fix(cluster): classify the independent dual terminal instead of fusin…
Jul 19, 2026
0d26b44
fix(cluster): close the frozen round when the independent dual's last…
Jul 19, 2026
ad67e65
fix(cluster): let a cancelled next-round member detach under terminal…
Jul 19, 2026
c53af5a
fix(cluster): deliver the frozen-round promotion through the RETIRE w…
Jul 19, 2026
9b651a9
fix(cluster): report the diverged descriptor tuple when an exact fini…
Jul 19, 2026
f40e54a
fix(cluster): enumerate the S-holder X upgrade as a legal grant finis…
Jul 19, 2026
7755173
Revert "fix(cluster): enumerate the S-holder X upgrade as a legal gra…
Jul 19, 2026
ec2c84d
fix(cluster): re-enter the convert queue when a stale X cover needs a…
Jul 19, 2026
a149625
fix(cluster): name the finish target state in the rolled-back grant d…
Jul 19, 2026
0804d57
fix(cluster): log the begin-time ownership context of a non-N legacy …
Jul 19, 2026
c3b1998
fix(cluster): keep the pinned retained PI mirror instead of clearing …
Jul 20, 2026
0d70fa7
fix(cluster): admit the direct-init IO window in the finish valid-ima…
Jul 20, 2026
e4e2b98
fix(cluster): republish a kept-pinned PI mirror at each legacy byte-c…
Jul 20, 2026
3d058d6
fix(cluster): name every silent refusal arm of the source-side REVOKE…
Jul 20, 2026
0994a2a
fix(cluster): deny in-flight legacy readers for PCM-X
Jul 20, 2026
f78f643
fix(cluster): count PCM own lifecycle at the exact grant begin and X …
Jul 19, 2026
94fe492
fix(cluster): retry post-handoff COMMIT_X
Jul 20, 2026
e56e2f1
fix(cluster): count exact PCM-X requester waits
Jul 20, 2026
3417a62
fix(cluster): unwind requester waits on late revoke barriers
Jul 20, 2026
3bcf484
fix(cluster): reuse stable successor S cover on reverify
Jul 20, 2026
e59cc4b
fix(cluster): stage pending-X denial replies on DATA owner
Jul 20, 2026
1b2c3c4
fix(cluster): keep PCM-X retained fallback current
Jul 20, 2026
505517f
fix(cluster): close four-node PCM-X liveness campaign
Jul 22, 2026
117a6dc
style(cluster): format PCM-X liveness closure
Jul 22, 2026
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
336 changes: 313 additions & 23 deletions src/backend/access/heap/heapam.c

Large diffs are not rendered by default.

73 changes: 38 additions & 35 deletions src/backend/access/heap/heapam_visibility.c
Original file line number Diff line number Diff line change
Expand Up @@ -828,37 +828,42 @@ cluster_satisfies_update_fork(HeapTuple htup, Buffer buffer, TM_Result *res)
* striping-off/below-floor collision with a remote inserter's xid; the
* pre-round-6 early return handed that collision to the PG-native cmin
* path -> "attempted to update invisible tuple". Only the no-remote-
* evidence route treats the raw match as our own insert.
* evidence route treats the raw match as our own insert. VACUUM freeze is
* stronger: it proves xmin committed even after that historical ITL slot is
* recycled, so only the exact FROZEN bit pair skips xmin resolution.
*/
cluster_visibility_resolve_tuple(buffer, tuple, raw_xmin, CLUSTER_VIS_XMIN, &r);
switch (cluster_vis_evidence_route(r.evidence, TransactionIdIsCurrentTransactionId(raw_xmin))) {
case CLUSTER_VIS_ROUTE_FAILCLOSED_UNKNOWN:
ereport(ERROR, (errcode(ERRCODE_CLUSTER_TT_STATUS_UNKNOWN),
errmsg("cluster TT slot recycled for xmin %u", raw_xmin),
errhint("ITL slot no longer maps to this xid; retry.")));
break;
case CLUSTER_VIS_ROUTE_NATIVE_SELF:
/* Our own insert: PG-native handles cmin / self-modified correctly. */
return false;
case CLUSTER_VIS_ROUTE_REMOTE_VERDICT:
switch (cluster_vis_update_xmin_verdict(r.status)) {
case CVV_INVISIBLE:
*res = TM_Invisible;
return true;
case CVV_FAILCLOSED_UNKNOWN:
if (cluster_vis_xmin_needs_resolution(tuple->t_infomask)) {
cluster_visibility_resolve_tuple(buffer, tuple, raw_xmin, CLUSTER_VIS_XMIN, &r);
switch (
cluster_vis_evidence_route(r.evidence, TransactionIdIsCurrentTransactionId(raw_xmin))) {
case CLUSTER_VIS_ROUTE_FAILCLOSED_UNKNOWN:
ereport(ERROR, (errcode(ERRCODE_CLUSTER_TT_STATUS_UNKNOWN),
errmsg("cluster TT status unknown for xmin %u", raw_xmin),
errhint("Remote commit_scn not yet propagated; retry or abort.")));
errmsg("cluster TT slot recycled for xmin %u", raw_xmin),
errhint("ITL slot no longer maps to this xid; retry.")));
break;
case CVV_VISIBLE:
xmin_remote_visible = true;
case CLUSTER_VIS_ROUTE_NATIVE_SELF:
/* Our own insert: PG-native handles cmin / self-modified correctly. */
return false;
case CLUSTER_VIS_ROUTE_REMOTE_VERDICT:
switch (cluster_vis_update_xmin_verdict(r.status)) {
case CVV_INVISIBLE:
*res = TM_Invisible;
return true;
case CVV_FAILCLOSED_UNKNOWN:
ereport(ERROR, (errcode(ERRCODE_CLUSTER_TT_STATUS_UNKNOWN),
errmsg("cluster TT status unknown for xmin %u", raw_xmin),
errhint("Remote commit_scn not yet propagated; retry or abort.")));
break;
case CVV_VISIBLE:
xmin_remote_visible = true;
break;
default:
break;
}
break;
default:
case CLUSTER_VIS_ROUTE_NATIVE:
break;
}
break;
case CLUSTER_VIS_ROUTE_NATIVE:
break;
}
/* xmin NONE/LOCAL: leave xmin to PG unless we must intercept the xmax. */

Expand Down Expand Up @@ -2465,17 +2470,15 @@ HeapTupleSatisfiesVacuumHorizon(HeapTuple htup, Buffer buffer, TransactionId *de

#ifdef USE_PGRAC_CLUSTER
/*
* spec-3.14 D5 (hole #2): a tuple with REMOTE writer evidence must never
* be judged DEAD by this node's local horizon -- overlapping xid spaces
* mean local oldest_xmin can pass a still-live remote xid (false-dead).
* Return HEAPTUPLE_LIVE: the RECENTLY_DEAD path re-tests dead_after
* against the LOCAL oldest_xmin / vistest (heap_prune_satisfies_vacuum)
* and would re-introduce the false-dead, so LIVE (unconditional keep) is
* the only safe verdict. Real reclamation waits for the cross-node
* vacuum-coordination spec (spec-3.14 §10). spec wrote RECENTLY_DEAD;
* LIVE is the implementation-time correction (dead_after re-test hazard).
* P0-28 / spec-3.14 §10: a peer may retain this exact TID across a
* PCM-X wait. ITL evidence is not a durable prune-horizon token: its slot
* may be reused before that peer reacquires the page, after which native
* xmin/CLOG checks can misclassify and recycle the target line pointer.
* Keep every shared-storage tuple until a real cluster horizon exists.
*/
if (cluster_storage_mode_enabled() && cluster_tuple_has_remote_evidence(buffer, tuple)) {
if (cluster_vis_prune_must_defer(cluster_storage_mode_enabled()
&& BufferIsValid(buffer) && !BufferIsLocal(buffer),
false)) {
cluster_vis_bump_prune_remote_keep_count();
return HEAPTUPLE_LIVE;
}
Expand Down
46 changes: 35 additions & 11 deletions src/backend/access/heap/hio.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,30 @@ cluster_hio_lease_target_block(Relation relation)
}
#endif

/* P0-20: acquire an ordered pair without ever sleeping on the second PCM-X
* conversion while the first content lock is held. BARRIER_CLOSED means the
* first page was frozen before this nested edge became visible. Release our
* first lock, resolve the second conversion alone, then retry in the original
* block order; pins remain owned by the caller throughout. */
static void
cluster_hio_lock_buffer_pair(Buffer first, Buffer second)
{
Assert(BufferIsValid(first));
Assert(BufferIsValid(second));
Assert(first != second);

for (;;)
{
LockBuffer(first, BUFFER_LOCK_EXCLUSIVE);
if (ClusterLockBufferExclusiveBarrierAware(second))
return;

LockBuffer(first, BUFFER_LOCK_UNLOCK);
LockBuffer(second, BUFFER_LOCK_EXCLUSIVE);
LockBuffer(second, BUFFER_LOCK_UNLOCK);
}
}

/*
* RelationPutHeapTuple - place tuple at specified page
*
Expand Down Expand Up @@ -233,10 +257,12 @@ GetVisibilityMapPins(Relation relation, Buffer buffer1, Buffer buffer2,
if (need_to_pin_buffer2)
visibilitymap_pin(relation, block2, vmbuffer2);

/* Relock buffers. */
LockBuffer(buffer1, BUFFER_LOCK_EXCLUSIVE);
/* Relock buffers. A cross-page relock keeps the same block order,
* but must unwind a frozen-barrier refusal from the second page. */
if (buffer2 != InvalidBuffer && buffer2 != buffer1)
LockBuffer(buffer2, BUFFER_LOCK_EXCLUSIVE);
cluster_hio_lock_buffer_pair(buffer1, buffer2);
else
LockBuffer(buffer1, BUFFER_LOCK_EXCLUSIVE);

/*
* If there are two buffers involved and we pinned just one of them,
Expand Down Expand Up @@ -716,17 +742,15 @@ RelationGetBufferForTuple(Relation relation, Size len,
buffer = ReadBuffer(relation, targetBlock);
if (PageIsAllVisible(BufferGetPage(buffer)))
visibilitymap_pin(relation, targetBlock, vmbuffer);
LockBuffer(otherBuffer, BUFFER_LOCK_EXCLUSIVE);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
cluster_hio_lock_buffer_pair(otherBuffer, buffer);
}
else
{
/* lock target buffer first */
buffer = ReadBuffer(relation, targetBlock);
if (PageIsAllVisible(BufferGetPage(buffer)))
visibilitymap_pin(relation, targetBlock, vmbuffer);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
LockBuffer(otherBuffer, BUFFER_LOCK_EXCLUSIVE);
cluster_hio_lock_buffer_pair(buffer, otherBuffer);
}

/*
Expand Down Expand Up @@ -900,8 +924,9 @@ RelationGetBufferForTuple(Relation relation, Size len,
{
/* released lock on target buffer above */
if (otherBuffer != InvalidBuffer)
LockBuffer(otherBuffer, BUFFER_LOCK_EXCLUSIVE);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
cluster_hio_lock_buffer_pair(otherBuffer, buffer);
else
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
recheckVmPins = true;
}
else if (otherBuffer != InvalidBuffer)
Expand All @@ -926,8 +951,7 @@ RelationGetBufferForTuple(Relation relation, Size len,
{
unlockedTargetBuffer = true;
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
LockBuffer(otherBuffer, BUFFER_LOCK_EXCLUSIVE);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
cluster_hio_lock_buffer_pair(otherBuffer, buffer);
}
recheckVmPins = true;
}
Expand Down
26 changes: 26 additions & 0 deletions src/backend/access/heap/visibilitymap.c
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,32 @@ visibilitymap_pin(Relation rel, BlockNumber heapBlk, Buffer *vmbuf)
*vmbuf = vm_readbuf(rel, mapBlock, true);
}

/*
* Repin the exact visibility-map block in a recently observed descriptor.
*
* Unlike visibilitymap_pin(), this never performs a mapping lookup, storage
* read, or relation extension. A heap mutation can therefore release its VM
* pin before a potentially blocking cluster PCM acquire, take heap content
* authority, and use this helper without introducing I/O below that lock. A
* false result means the descriptor was invalidated or retagged; the caller
* must release heap content authority and retry through visibilitymap_pin().
*/
bool
visibilitymap_pin_recent(Relation rel, BlockNumber heapBlk, Buffer recent_buffer,
Buffer *vmbuf)
{
BlockNumber mapBlock = HEAPBLK_TO_MAPBLOCK(heapBlk);

if (rel == NULL || vmbuf == NULL || BufferIsValid(*vmbuf)
|| !BufferIsValid(recent_buffer))
return false;
if (!ReadRecentBuffer(RelationGetSmgr(rel)->smgr_rlocator.locator,
VISIBILITYMAP_FORKNUM, mapBlock, recent_buffer))
return false;
*vmbuf = recent_buffer;
return true;
}

/*
* visibilitymap_pin_ok - do we already have the correct page pinned?
*
Expand Down
2 changes: 1 addition & 1 deletion src/backend/cluster/cluster_cr_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -1049,7 +1049,7 @@ cr_serve_slot(ClusterLmsCrSlot *slot)
CLUSTER_INJECTION_POINT("cluster-lms-cr-construct");
if (!cluster_injection_should_skip("cluster-lms-cr-construct")
&& cluster_crossnode_cr_data_plane
&& cluster_bufmgr_copy_block_for_gcs(slot->tag, &page_lsn, cur_copy.data)) {
&& cluster_bufmgr_copy_block_for_gcs(slot->tag, &page_lsn, cur_copy.data, NULL)) {
PG_TRY();
{
cluster_cr_construct_page_for_server(cur_copy.data, slot->read_scn, slot->tag,
Expand Down
56 changes: 55 additions & 1 deletion src/backend/cluster/cluster_debug.c
Original file line number Diff line number Diff line change
Expand Up @@ -1451,6 +1451,8 @@ dump_lms(ReturnSetInfo *rsinfo)
fmt_int64((int64)cluster_lms_obs_get_outbound_not_admitted(-1)));
emit_row(rsinfo, "lms", "lms_outbound_requeue_drop_count",
fmt_int64((int64)cluster_lms_obs_get_outbound_requeue_drop(-1)));
emit_row(rsinfo, "lms", "lms_outbound_cap_guard_drop_count",
fmt_int64((int64)cluster_lms_obs_get_outbound_cap_guard_drop(-1)));
{
int w, hb;

Expand All @@ -1474,6 +1476,9 @@ dump_lms(ReturnSetInfo *rsinfo)
snprintf(wkey, sizeof(wkey), "lms_outbound_requeue_drop_count_w%d", w);
emit_row(rsinfo, "lms", wkey,
fmt_int64((int64)cluster_lms_obs_get_outbound_requeue_drop(w)));
snprintf(wkey, sizeof(wkey), "lms_outbound_cap_guard_drop_count_w%d", w);
emit_row(rsinfo, "lms", wkey,
fmt_int64((int64)cluster_lms_obs_get_outbound_cap_guard_drop(w)));
}

/* Inline-serve duration histogram: aggregate 16 rows + per live
Expand Down Expand Up @@ -1995,15 +2000,60 @@ dump_pcm(ReturnSetInfo *rsinfo)
emit_row(rsinfo, "pcm", "wm_prov_insert_fail_count",
fmt_int64((int64)cluster_pcm_get_wm_prov_insert_fail_count()));

/* PCM-X queue core: 30 stable keys in the existing pcm category. */
/* PCM-X queue core: 30 stable integer keys in the existing pcm category,
* plus one text-valued fail-closed provenance key (excluded from the
* integer-only snapshot tooling in the TAP suite). */
{
PcmXStatsSnapshot stats = { 0 };
PcmXRuntimeSnapshot runtime = cluster_pcm_x_runtime_snapshot();
char fail_closed_site[PCM_X_FAIL_CLOSED_SITE_LEN];

(void)cluster_pcm_x_stats_snapshot(&stats);
emit_row(rsinfo, "pcm", "pcm_x_runtime_state", fmt_int32((int32)runtime.state));
emit_row(rsinfo, "pcm", "pcm_x_runtime_generation",
fmt_int64((int64)runtime.gate_generation));
if (!cluster_pcm_x_runtime_fail_closed_site(fail_closed_site, sizeof(fail_closed_site)))
strlcpy(fail_closed_site, "(none)", sizeof(fail_closed_site));
emit_row(rsinfo, "pcm", "pcm_x_runtime_fail_closed_site", fail_closed_site);

/* Text-valued per-slot diagnostic rows; present only while master tag
* or ticket slots are occupied, so the quiescent key count above is
* unchanged. */
{
Size cursor = 0;
Size index = 0;
char line[768];
char key[64];

while (cluster_pcm_x_master_tag_debug_next(&cursor, &index, line, sizeof(line))) {
snprintf(key, sizeof(key), "pcm_x_tag_%zu", index);
emit_row(rsinfo, "pcm", key, line);
}
cursor = 0;
while (cluster_pcm_x_master_ticket_debug_next(&cursor, &index, line, sizeof(line))) {
snprintf(key, sizeof(key), "pcm_x_ticket_%zu", index);
emit_row(rsinfo, "pcm", key, line);
}
cursor = 0;
while (cluster_pcm_x_local_tag_debug_next(&cursor, &index, line, sizeof(line))) {
snprintf(key, sizeof(key), "pcm_x_ltag_%zu", index);
emit_row(rsinfo, "pcm", key, line);
}
{
uint32 note_op = 0;
uint32 note_result = 0;
uint32 note_count = 0;
uint64 note_ticket = 0;

if (cluster_pcm_x_terminal_note_read(&note_op, &note_result, &note_ticket,
&note_count)) {
snprintf(line, sizeof(line),
"op=%u result=%u ticket=" UINT64_FORMAT " count=%u", note_op,
note_result, note_ticket, note_count);
emit_row(rsinfo, "pcm", "pcm_x_terminal_last_note", line);
}
}
}
emit_row(rsinfo, "pcm", "pcm_x_queue_enqueue_count", fmt_int64((int64)stats.enqueue_count));
emit_row(rsinfo, "pcm", "pcm_x_queue_admit_count", fmt_int64((int64)stats.admit_count));
emit_row(rsinfo, "pcm", "pcm_x_queue_confirm_count", fmt_int64((int64)stats.confirm_count));
Expand Down Expand Up @@ -2043,6 +2093,8 @@ dump_pcm(ReturnSetInfo *rsinfo)
emit_row(rsinfo, "pcm", "pcm_x_own_busy_count", fmt_int64((int64)stats.own_busy_count));
emit_row(rsinfo, "pcm", "pcm_x_own_corrupt_count",
fmt_int64((int64)stats.own_corrupt_count));
emit_row(rsinfo, "pcm", "pcm_x_queue_barrier_unwind_count",
fmt_int64((int64)stats.barrier_unwind_count));
}
}

Expand Down Expand Up @@ -2354,6 +2406,8 @@ dump_gcs(ReturnSetInfo *rsinfo)
fmt_int64((int64)cluster_gcs_get_pi_watermark_advance_count()));
emit_row(rsinfo, "gcs", "pi_watermark_retire_count",
fmt_int64((int64)cluster_gcs_get_pi_watermark_retire_count()));
emit_row(rsinfo, "gcs", "pi_durable_note_apply_count",
fmt_int64((int64)cluster_gcs_get_pi_durable_note_apply_count()));
emit_row(rsinfo, "gcs", "lost_write_detected_count",
fmt_int64((int64)cluster_gcs_get_lost_write_detected_count()));
emit_row(rsinfo, "gcs", "lost_write_avoid_count",
Expand Down
Loading
Loading