Skip to content

Commit 12ab61c

Browse files
authored
Merge pull request #62 from sqlrush/fix/nightly-clusterquad-xid-striping
fix(cluster): PCM-X convert-queue closure — dirty-N flush, churn recovery, self-ACK, rejoin contract
2 parents c3d10b7 + 205a3e3 commit 12ab61c

22 files changed

Lines changed: 771 additions & 98 deletions

.github/workflows/nightly.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,12 @@ jobs:
247247
# shard the same commit). Own shard: three ClusterPair bring-ups
248248
# + the t/403 sustained write phase need the wall clock.
249249
- { name: stage7-s3-wave, ranges: "401-403", unit: false, regress: false }
250+
# t/400 PCM-X convert-queue 4-node liveness (ClusterQuad bring-up)
251+
# + t/404 crash-rejoin stale-read/write fence (2-node with a full
252+
# crash/restart cycle). Both were in NO CI matrix (L342: every new
253+
# t/ file lands in a shard the same commit). Own shard: the quad
254+
# formation and the rejoin crash cycle dominate the wall clock.
255+
- { name: stage7-queue-liveness-rejoin, ranges: "400 404", unit: false, regress: false }
250256
steps:
251257
- name: Checkout
252258
uses: actions/checkout@v4

src/backend/cluster/cluster_gcs_block.c

Lines changed: 116 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8121,12 +8121,18 @@ gcs_block_pcm_x_requester_clear_wait(GcsBlockPcmXRequesterCleanupContext *cleanu
81218121
cleanup->wait_published = false;
81228122
}
81238123

8124+
/* Bound the STALE cancel refresh loop: each retry means the slot churned
8125+
* again inside a lock-to-lock window, so more than a few consecutive hits
8126+
* are no longer plausible scheduling and keep the fail-closed verdict. */
8127+
#define GCS_BLOCK_PCM_X_CLEANUP_REFRESH_MAX 3
8128+
81248129
static PcmXQueueResult
81258130
gcs_block_pcm_x_requester_cleanup_impl(GcsBlockPcmXRequesterCleanupContext *cleanup,
81268131
bool owner_exit)
81278132
{
81288133
GcsBlockPcmXCleanupAction action;
81298134
PcmXLocalHandle promoted;
8135+
PcmXLocalHandle refreshed;
81308136
PcmXQueueResult result;
81318137
PcmXRuntimeSnapshot runtime;
81328138

@@ -8204,12 +8210,32 @@ gcs_block_pcm_x_requester_cleanup_impl(GcsBlockPcmXRequesterCleanupContext *clea
82048210
return PCM_X_QUEUE_NOT_READY;
82058211
}
82068212
if (action == GCS_BLOCK_PCM_X_CLEANUP_CANCEL_LOCAL) {
8207-
memset(&promoted, 0, sizeof(promoted));
8208-
result = cluster_pcm_x_local_cancel_exact(&cleanup->handle, &promoted);
8209-
cluster_pcm_x_stats_note_queue_result(result);
8210-
if (result == PCM_X_QUEUE_OK || result == PCM_X_QUEUE_DUPLICATE) {
8211-
result = cluster_pcm_x_local_detach_terminal_exact(&cleanup->handle);
8213+
int refresh_attempts = 0;
8214+
8215+
for (;;) {
8216+
memset(&promoted, 0, sizeof(promoted));
8217+
result = cluster_pcm_x_local_cancel_exact(&cleanup->handle, &promoted);
8218+
cluster_pcm_x_stats_note_queue_result(result);
8219+
if (result == PCM_X_QUEUE_OK || result == PCM_X_QUEUE_DUPLICATE) {
8220+
result = cluster_pcm_x_local_detach_terminal_exact(&cleanup->handle);
8221+
cluster_pcm_x_stats_note_queue_result(result);
8222+
}
8223+
if (result != PCM_X_QUEUE_STALE
8224+
|| refresh_attempts >= GCS_BLOCK_PCM_X_CLEANUP_REFRESH_MAX)
8225+
break;
8226+
/* STALE proves the membership advanced (promotion or round churn)
8227+
* under an identity that is still exactly ours. Cancelling
8228+
* releases rather than confers authority, so rebuild the handle
8229+
* from the live slot and retry; a vanished membership is already
8230+
* terminal and leaves nothing to cancel. */
8231+
refresh_attempts++;
8232+
result = cluster_pcm_x_local_lookup_exact(&cleanup->handle.identity, &refreshed);
82128233
cluster_pcm_x_stats_note_queue_result(result);
8234+
if (result == PCM_X_QUEUE_NOT_FOUND)
8235+
break;
8236+
if (result != PCM_X_QUEUE_OK)
8237+
break;
8238+
cleanup->handle = refreshed;
82138239
}
82148240
if (result != PCM_X_QUEUE_OK && result != PCM_X_QUEUE_NOT_FOUND) {
82158241
runtime = cluster_pcm_x_runtime_snapshot();
@@ -8687,6 +8713,26 @@ gcs_block_pcm_x_acquire_writer_impl(BufferDesc *buf, PcmXLocalWriterClaim *claim
86878713
} else {
86888714
retry_action = cluster_gcs_pcm_x_requester_retry_action(
86898715
GCS_BLOCK_PCM_X_RETRY_SITE_FOLLOWER_SNAPSHOT, result);
8716+
if (retry_action == GCS_BLOCK_PCM_X_RETRY_REFRESH_ROLE) {
8717+
/* The snapshot proved this handle no longer byte-matches
8718+
* its membership slot -- the same promotion / round churn
8719+
* the claim site recovers from. Rebuild the handle and
8720+
* re-dispatch by its current role instead of closing the
8721+
* runtime over a normal FIFO progress event. */
8722+
fail_site = "follower-refresh-lookup";
8723+
result = cluster_pcm_x_local_lookup_exact(&handle.identity, &fresh_handle);
8724+
cluster_pcm_x_stats_note_queue_result(result);
8725+
if (result == PCM_X_QUEUE_OK) {
8726+
fail_site = "follower-refresh-compare";
8727+
if (!cluster_gcs_pcm_x_role_refresh_exact(&handle, &fresh_handle))
8728+
goto requester_fail_closed;
8729+
handle = fresh_handle;
8730+
gcs_block_pcm_x_requester_cleanup_context.handle = handle;
8731+
goto requester_role_dispatch;
8732+
}
8733+
retry_action = cluster_gcs_pcm_x_requester_retry_action(
8734+
GCS_BLOCK_PCM_X_RETRY_SITE_ROLE_REFRESH, result);
8735+
}
86908736
if (retry_action != GCS_BLOCK_PCM_X_RETRY_WAIT)
86918737
goto requester_fail_closed;
86928738
}
@@ -9266,6 +9312,13 @@ gcs_block_pcm_x_master_drive_fail_closed(PcmXQueueResult result)
92669312
}
92679313

92689314

9315+
/* Process-local BAD_STATE damping for the drive dispatch (one table per
9316+
* driving process; LMON's periodic retry tick is the guaranteed observer
9317+
* that escalates a persisting per-ticket anomaly to the fail-closed verdict). */
9318+
static GcsBlockPcmXDriveAnomaly
9319+
gcs_block_pcm_x_drive_anomaly_table[GCS_BLOCK_PCM_X_DRIVE_ANOMALY_SLOTS];
9320+
9321+
92699322
static PcmXQueueResult
92709323
gcs_block_pcm_x_master_authority(const PcmXMasterDriveSnapshot *snapshot,
92719324
PcmAuthoritySnapshot *authority_out, uint32 *holders_out,
@@ -9305,8 +9358,22 @@ gcs_block_pcm_x_ensure_pending_x_claim(const PcmXMasterDriveSnapshot *snapshot)
93059358
if (claimed) {
93069359
if (!cluster_pcm_lock_queue_pending_x_exact(snapshot->ref.identity.tag,
93079360
snapshot->ref.identity.node_id,
9308-
snapshot->ref.handle.ticket_id))
9309-
return PCM_X_QUEUE_BAD_STATE;
9361+
snapshot->ref.handle.ticket_id)) {
9362+
/* Mirror the reserve-path recheck below: CANCEL clears the GRD
9363+
* cookie before it finalizes the ticket, so a missing cookie can
9364+
* be an in-progress cancel rather than corruption. Re-read the
9365+
* ticket under its own domain lock; durable cancel/terminal
9366+
* progress is retryable, and only a ticket that still claims with
9367+
* no cookie stays anomalous for the caller's damping streak. */
9368+
claimed = false;
9369+
result = cluster_pcm_x_master_pending_x_claim_state_exact(&snapshot->ref, &claimed);
9370+
if (result == PCM_X_QUEUE_NOT_READY || result == PCM_X_QUEUE_STALE
9371+
|| result == PCM_X_QUEUE_NOT_FOUND || result == PCM_X_QUEUE_RETIRED)
9372+
return PCM_X_QUEUE_NOT_READY;
9373+
if (result != PCM_X_QUEUE_OK)
9374+
return result;
9375+
return claimed ? PCM_X_QUEUE_BAD_STATE : PCM_X_QUEUE_NOT_READY;
9376+
}
93109377
return PCM_X_QUEUE_OK;
93119378
}
93129379

@@ -9639,6 +9706,11 @@ gcs_block_pcm_x_master_drive_tag(const BufferTag *tag, uint64 cluster_epoch)
96399706
result = cluster_pcm_x_master_promote_head_exact(tag, cluster_epoch, &active);
96409707
cluster_pcm_x_stats_note_queue_result(result);
96419708
if (result != PCM_X_QUEUE_OK && result != PCM_X_QUEUE_BUSY) {
9709+
/* No promotable head means any earlier per-ticket anomaly for this
9710+
* tag has resolved (cancelled / retired); settle its streaks. */
9711+
if (result == PCM_X_QUEUE_NOT_FOUND)
9712+
cluster_gcs_pcm_x_drive_anomaly_settle(gcs_block_pcm_x_drive_anomaly_table,
9713+
GCS_BLOCK_PCM_X_DRIVE_ANOMALY_SLOTS, tag);
96429714
gcs_block_pcm_x_master_drive_fail_closed(result);
96439715
return;
96449716
}
@@ -9662,6 +9734,21 @@ gcs_block_pcm_x_master_drive_tag(const BufferTag *tag, uint64 cluster_epoch)
96629734
else
96639735
result = PCM_X_QUEUE_CORRUPT;
96649736
cluster_pcm_x_stats_note_queue_result(result);
9737+
/* Only definite drive progress settles the tag; indeterminate results
9738+
* (NOT_READY / BUSY / STALE) must not reset a live streak, or a real
9739+
* wedge interleaved with transients would never fuse. */
9740+
if (result == PCM_X_QUEUE_OK || result == PCM_X_QUEUE_DUPLICATE)
9741+
cluster_gcs_pcm_x_drive_anomaly_settle(gcs_block_pcm_x_drive_anomaly_table,
9742+
GCS_BLOCK_PCM_X_DRIVE_ANOMALY_SLOTS, tag);
9743+
/* A lone dispatch BAD_STATE can be another actor's two-phase window (an
9744+
* in-progress claimed cancel, an identity-keyed serve-path clear). Damp
9745+
* it per ticket and let the periodic re-drive re-observe; only a streak
9746+
* that survives consecutive ticks reaches the runtime fuse. */
9747+
if (result == PCM_X_QUEUE_BAD_STATE
9748+
&& !cluster_gcs_pcm_x_drive_anomaly_note(gcs_block_pcm_x_drive_anomaly_table,
9749+
GCS_BLOCK_PCM_X_DRIVE_ANOMALY_SLOTS, tag,
9750+
snapshot.ref.handle.ticket_id))
9751+
return;
96659752
gcs_block_pcm_x_master_drive_fail_closed(result);
96669753
}
96679754

@@ -13255,10 +13342,28 @@ gcs_block_invalidate_execute(const GcsBlockInvalidatePayload *inv)
1325513342
GcsBlockInvalidateAckPayloadSetPageScn(&ack, page_scn); /* spec-2.41 D3 — SCN carrier @52 */
1325613343
ack.checksum = gcs_block_compute_invalidate_ack_checksum(&ack);
1325713344

13258-
cluster_gcs_block_note_send_outcome(
13259-
GCS_BLOCK_SEND_FAMILY_INVALIDATE,
13260-
cluster_ic_send_envelope(PGRAC_IC_MSG_GCS_BLOCK_INVALIDATE_ACK, inv->master_node, &ack,
13261-
sizeof(ack)));
13345+
/*
13346+
* The generic IC path deliberately treats dest=self as a successful
13347+
* no-op. That is not delivery for this application ACK: a resource
13348+
* master which is also an S holder must consume its own drop proof before
13349+
* it can advance the PCM-X transfer. Stage only that local arm through
13350+
* the tag-sharded DATA ring; its LMS worker performs real loopback
13351+
* dispatch and preserves same-tag ordering. Keep remote ACKs on their
13352+
* existing direct DATA connection.
13353+
*/
13354+
if (inv->master_node == cluster_node_id) {
13355+
ClusterICSendResult local_result
13356+
= cluster_grd_outbound_enqueue_backend_msg(PGRAC_IC_MSG_GCS_BLOCK_INVALIDATE_ACK,
13357+
(uint32)inv->master_node, &ack, sizeof(ack))
13358+
? CLUSTER_IC_SEND_DONE
13359+
: CLUSTER_IC_SEND_NOT_ADMITTED;
13360+
13361+
cluster_gcs_block_note_send_outcome(GCS_BLOCK_SEND_FAMILY_INVALIDATE, local_result);
13362+
} else
13363+
cluster_gcs_block_note_send_outcome(
13364+
GCS_BLOCK_SEND_FAMILY_INVALIDATE,
13365+
cluster_ic_send_envelope(PGRAC_IC_MSG_GCS_BLOCK_INVALIDATE_ACK, inv->master_node, &ack,
13366+
sizeof(ack)));
1326213367
return true;
1326313368
}
1326413369

src/backend/cluster/cluster_gcs_block_shard.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ StaticAssertDecl(offsetof(GcsBlockForwardPayload, tag) == 16,
6161
"spec-7.3 D4: GcsBlockForwardPayload.tag offset moved");
6262
StaticAssertDecl(offsetof(GcsBlockInvalidatePayload, tag) == 16,
6363
"spec-7.3 D4: GcsBlockInvalidatePayload.tag offset moved");
64+
StaticAssertDecl(offsetof(GcsBlockInvalidateAckPayload, tag) == 16,
65+
"spec-7.3 D4: GcsBlockInvalidateAckPayload.tag offset moved");
6466
StaticAssertDecl(offsetof(GcsBlockDonePayload, tag) == 16,
6567
"GCS-race round-2 review F4: GcsBlockDonePayload.tag offset moved");
6668
StaticAssertDecl(offsetof(PcmXWaitIdentity, tag) == 0, "PCM-X wait tag must lead payloads");
@@ -99,6 +101,11 @@ cluster_gcs_block_payload_shard(uint8 msg_type, const void *payload, uint16 payl
99101
return -1;
100102
tag = &((const GcsBlockInvalidatePayload *)payload)->tag;
101103
break;
104+
case PGRAC_IC_MSG_GCS_BLOCK_INVALIDATE_ACK:
105+
if (payload_len != sizeof(GcsBlockInvalidateAckPayload))
106+
return -1;
107+
tag = &((const GcsBlockInvalidateAckPayload *)payload)->tag;
108+
break;
102109
case PGRAC_IC_MSG_GCS_BLOCK_DONE:
103110
/* GCS-race round-2 review F4: the completion proof is a staged
104111
* tag-carrying frame like REQUEST -- without this case every DONE

src/backend/cluster/cluster_pcm_x_convert.c

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5620,6 +5620,8 @@ pcm_x_master_blocker_stage_begin_precheck_locked(const PcmXMasterTicketSlot *tic
56205620
PcmXAllocatorView blocker_view;
56215621
uint64 current_generation;
56225622
uint32 state;
5623+
bool current_probe_source;
5624+
bool published_source;
56235625

56245626
if (ticket == NULL || begin == NULL || !pcm_x_master_blocker_stage_metadata_valid(ticket)
56255627
|| !pcm_x_allocator_view(PCM_X_ALLOC_BLOCKER, &blocker_view))
@@ -5653,7 +5655,8 @@ pcm_x_master_blocker_stage_begin_precheck_locked(const PcmXMasterTicketSlot *tic
56535655
? PCM_X_QUEUE_DUPLICATE
56545656
: PCM_X_QUEUE_STALE;
56555657
}
5656-
if (!pcm_x_master_probe_source_exact(ticket, source_node, source_session)
5658+
current_probe_source = pcm_x_master_probe_source_exact(ticket, source_node, source_session);
5659+
if (!current_probe_source
56575660
&& !pcm_x_master_blocker_ack_replay_exact(ticket, source_node, source_session))
56585661
return PCM_X_QUEUE_STALE;
56595662
if (ticket->blocker_stage_set_generation != 0) {
@@ -5666,14 +5669,27 @@ pcm_x_master_blocker_stage_begin_precheck_locked(const PcmXMasterTicketSlot *tic
56665669
return begin->set_generation <= ticket->blocker_stage_set_generation ? PCM_X_QUEUE_STALE
56675670
: PCM_X_QUEUE_BUSY;
56685671
}
5669-
if (begin->set_generation == current_generation) {
5672+
published_source
5673+
= pcm_x_master_blocker_published_source_exact(ticket, source_node, source_session);
5674+
if (begin->set_generation == current_generation && published_source) {
56705675
return begin->nblockers == ticket->blocker_count
56715676
&& begin->set_crc32c == ticket->blocker_set_crc32c
5672-
&& pcm_x_master_blocker_published_source_exact(ticket, source_node,
5673-
source_session)
56745677
? PCM_X_QUEUE_DUPLICATE
56755678
: PCM_X_QUEUE_STALE;
56765679
}
5680+
/* set_generation belongs to the holder's local tag, so another exact
5681+
* holder may legitimately publish the same (or a lower) numeric value.
5682+
* The authenticated source tuple completes its namespace. Before
5683+
* replacing a previous holder's set, require that exact set to have
5684+
* reached the graph-commit boundary. */
5685+
if (current_probe_source && !published_source) {
5686+
if (current_generation != 0 && ticket->graph_generation == 0)
5687+
return PCM_X_QUEUE_NOT_READY;
5688+
return PCM_X_QUEUE_OK;
5689+
}
5690+
/* A non-current source can only be replaying the last published set. */
5691+
if (!current_probe_source)
5692+
return PCM_X_QUEUE_STALE;
56775693
if (begin->set_generation < current_generation)
56785694
return PCM_X_QUEUE_STALE;
56795695
if (current_generation == UINT64_MAX || begin->set_generation == UINT64_MAX)
@@ -8493,6 +8509,32 @@ cluster_pcm_x_master_terminal_leg_arm_exact(const PcmXTicketRef *ref, PcmXTermin
84938509
result = PCM_X_QUEUE_STALE;
84948510
goto arm_done;
84958511
}
8512+
/* The retry driver scans the fixed participant bitmap from node zero on
8513+
* every pass. Once an earlier responder has ACKed, a later responder may
8514+
* own the one reliable leg. Classify the completed responder before the
8515+
* generic cross-responder BUSY check, otherwise that later leg permanently
8516+
* hides itself behind the first completed node. */
8517+
if ((kind == PCM_X_TERMINAL_LEG_DRAIN
8518+
&& (state == PCM_XT_RETIRE_CREDIT || (ticket->drained_nodes_bitmap & responder_bit) != 0))
8519+
|| (kind == PCM_X_TERMINAL_LEG_RETIRE && state == PCM_XT_RETIRE_CREDIT
8520+
&& (ticket->retire_acked_nodes_bitmap & responder_bit) != 0)) {
8521+
/* A same-phase leg cannot remain armed after its responder bit commits.
8522+
* Do not let the retry classification conceal that structural split. */
8523+
if (!pcm_x_master_terminal_leg_is_clear(&ticket->reliable)
8524+
&& ticket->reliable.pending_opcode == (uint16)kind
8525+
&& ticket->reliable.phase == (uint16)kind
8526+
&& ticket->reliable.expected_responder_node == responder_node) {
8527+
result = PCM_X_QUEUE_CORRUPT;
8528+
fail_closed = true;
8529+
} else
8530+
result = PCM_X_QUEUE_NOT_READY;
8531+
goto arm_done;
8532+
}
8533+
if ((kind == PCM_X_TERMINAL_LEG_DRAIN && state == PCM_XT_RETIRE_CREDIT)
8534+
|| (kind == PCM_X_TERMINAL_LEG_RETIRE && state != PCM_XT_RETIRE_CREDIT)) {
8535+
result = PCM_X_QUEUE_NOT_READY;
8536+
goto arm_done;
8537+
}
84968538
if (!pcm_x_master_terminal_leg_is_clear(&ticket->reliable)) {
84978539
if (ticket->reliable.pending_opcode == (uint16)kind
84988540
&& ticket->reliable.phase == (uint16)kind
@@ -8511,18 +8553,6 @@ cluster_pcm_x_master_terminal_leg_arm_exact(const PcmXTicketRef *ref, PcmXTermin
85118553
result = PCM_X_QUEUE_BUSY;
85128554
goto arm_done;
85138555
}
8514-
if (kind == PCM_X_TERMINAL_LEG_DRAIN) {
8515-
if (state == PCM_XT_RETIRE_CREDIT || (ticket->drained_nodes_bitmap & responder_bit) != 0) {
8516-
result = PCM_X_QUEUE_NOT_READY;
8517-
goto arm_done;
8518-
}
8519-
} else {
8520-
if (state != PCM_XT_RETIRE_CREDIT
8521-
|| (ticket->retire_acked_nodes_bitmap & responder_bit) != 0) {
8522-
result = PCM_X_QUEUE_NOT_READY;
8523-
goto arm_done;
8524-
}
8525-
}
85268556
if (!cluster_pcm_x_generation_next(ticket->reliable.state_sequence, &next_sequence)) {
85278557
result = PCM_X_QUEUE_COUNTER_EXHAUSTED;
85288558
fail_closed = true;

src/backend/storage/buffer/bufmgr.c

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11205,7 +11205,7 @@ cluster_bufmgr_pcm_own_prepare_n_source_image(BufferDesc *buf,
1120511205
char block_data[BLCKSZ], XLogRecPtr *out_page_lsn,
1120611206
uint64 *out_page_scn)
1120711207
{
11208-
PGAlignedBlock scratch;
11208+
PGIOAlignedBlock scratch;
1120911209
BufferTag tag;
1121011210
ClusterPcmOwnSnapshot live;
1121111211
ClusterPcmOwnResult abort_result;
@@ -11233,11 +11233,34 @@ cluster_bufmgr_pcm_own_prepare_n_source_image(BufferDesc *buf,
1123311233
return CLUSTER_PCM_OWN_NOT_READY;
1123411234

1123511235
tag = expected_n->tag;
11236+
/* The dirty-page branch below may pin under the header lock. */
11237+
ReservePrivateRefCountEntry();
11238+
ResourceOwnerEnlargeBuffers(CurrentResourceOwner);
1123611239
buf_state = LockBufHdr(buf);
1123711240
if (!cluster_pcm_own_snapshot_matches_locked(buf, expected_n) || (buf_state & BM_VALID) == 0)
1123811241
result = CLUSTER_PCM_OWN_STALE;
11239-
else if ((buf_state & (BM_DIRTY | BM_JUST_DIRTIED | BM_CHECKPOINT_NEEDED | BM_IO_ERROR)) != 0)
11242+
else if ((buf_state & BM_IO_ERROR) != 0)
1124011243
result = CLUSTER_PCM_OWN_CORRUPT;
11244+
else if ((buf_state & (BM_DIRTY | BM_JUST_DIRTIED | BM_CHECKPOINT_NEEDED)) != 0)
11245+
{
11246+
/*
11247+
* PGRAC: a dirty N page here is legitimate, not corruption evidence:
11248+
* relation extension (PageInit + MarkBufferDirty) and recovery redo
11249+
* both dirty a page before any PCM grant exists, so the first
11250+
* cluster-aware writer meets its own pre-grant dirt. The N-source
11251+
* contract serves STORAGE bytes and overwrites the resident copy, so
11252+
* consuming the page now would discard the newer local bytes (a lost
11253+
* write). Push the local bytes out first (FlushBuffer is WAL-first)
11254+
* and report BUSY: one flush converges the state and the image pump
11255+
* retries against a clean page.
11256+
*/
11257+
PinBuffer_Locked(buf); /* consumes the buffer header lock */
11258+
LWLockAcquire(BufferDescriptorGetContentLock(buf), LW_SHARED);
11259+
FlushBuffer(buf, NULL, IOOBJECT_RELATION, IOCONTEXT_NORMAL);
11260+
LWLockRelease(BufferDescriptorGetContentLock(buf));
11261+
UnpinBuffer(buf);
11262+
return CLUSTER_PCM_OWN_BUSY;
11263+
}
1124111264
else if ((buf_state & BM_IO_IN_PROGRESS) != 0)
1124211265
result = CLUSTER_PCM_OWN_BUSY;
1124311266
else {
@@ -11257,7 +11280,9 @@ cluster_bufmgr_pcm_own_prepare_n_source_image(BufferDesc *buf,
1125711280
smgrread(reln, BufTagGetForkNum(&tag), tag.blockNum, scratch.data);
1125811281
if (!PageIsVerifiedExtended((Page)scratch.data, tag.blockNum,
1125911282
PIV_LOG_WARNING | PIV_REPORT_STAT))
11283+
{
1126011284
result = CLUSTER_PCM_OWN_CORRUPT;
11285+
}
1126111286

1126211287
if (result == CLUSTER_PCM_OWN_OK) {
1126311288
LWLockAcquire(content_lock, LW_EXCLUSIVE);
@@ -11267,9 +11292,16 @@ cluster_bufmgr_pcm_own_prepare_n_source_image(BufferDesc *buf,
1126711292
|| live.flags != PCM_OWN_FLAG_REVOKING || live.pcm_state != (uint8)PCM_STATE_N
1126811293
|| (buf_state & BM_VALID) == 0)
1126911294
result = CLUSTER_PCM_OWN_STALE;
11270-
else if ((buf_state & (BM_DIRTY | BM_JUST_DIRTIED | BM_CHECKPOINT_NEEDED | BM_IO_ERROR))
11271-
!= 0)
11295+
else if ((buf_state & BM_IO_ERROR) != 0)
1127211296
result = CLUSTER_PCM_OWN_CORRUPT;
11297+
else if ((buf_state & (BM_DIRTY | BM_JUST_DIRTIED | BM_CHECKPOINT_NEEDED)) != 0)
11298+
{
11299+
/* PGRAC: REVOKING already blocks data writes, so dirt appearing
11300+
* between the flush above and this recheck can only be an
11301+
* idempotent hint-bit write. Retry via BUSY; the next pass
11302+
* flushes it and converges. */
11303+
result = CLUSTER_PCM_OWN_BUSY;
11304+
}
1127311305
else if ((buf_state & BM_IO_IN_PROGRESS) != 0)
1127411306
result = CLUSTER_PCM_OWN_BUSY;
1127511307
else {

0 commit comments

Comments
 (0)