@@ -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+
81248129static PcmXQueueResult
81258130gcs_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+
92699322static PcmXQueueResult
92709323gcs_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
0 commit comments