@@ -430,6 +430,19 @@ cluster_clean_leave_announce_payload_valid(const ClusterLeaveAnnouncePayload *p)
430430 * known kind (an unknown value fails closed like a bad version). */
431431 if (p -> producer_kind > CLUSTER_LEAVE_PRODUCER_SHUTDOWN )
432432 return false;
433+ if (p -> preflight > CLUSTER_PHASE1_FULL_STOP_WIRE_RECEIPT )
434+ return false;
435+ if (p -> preflight >= CLUSTER_PHASE1_FULL_STOP_WIRE_RELEASE
436+ && (p -> producer_kind != CLUSTER_LEAVE_PRODUCER_SHUTDOWN
437+ || p -> _pad0 != 0
438+ || p -> _pad1 [0 ] != 0
439+ || p -> _pad1 [1 ] != 0
440+ || p -> leaving_node_id >= CLUSTER_PHASE1_FULL_STOP_MEMBER_COUNT
441+ || p -> leave_epoch != 0
442+ || p -> cssd_dead_generation != 0
443+ || p -> leave_nonce == 0
444+ || p -> leave_nonce == UINT64_MAX ))
445+ return false;
433446 return true;
434447}
435448
@@ -464,5 +477,263 @@ cluster_clean_leave_ack_payload_valid(const ClusterLeaveAckPayload *p)
464477 return false;
465478 if (p -> leaving_node_id < 0 || p -> leaving_node_id >= CLUSTER_CLEAN_LEAVE_MAX_NODE_ID )
466479 return false;
480+ if (p -> phase1_round != 0
481+ && p -> phase1_round != CLUSTER_PHASE1_FULL_STOP_WIRE_RELEASE )
482+ return false;
483+ if (p -> phase1_round == CLUSTER_PHASE1_FULL_STOP_WIRE_RELEASE
484+ && (p -> _pad0 != 0
485+ || p -> _pad1 != 0
486+ || p -> survivor_node_id >= CLUSTER_PHASE1_FULL_STOP_MEMBER_COUNT
487+ || p -> leaving_node_id >= CLUSTER_PHASE1_FULL_STOP_MEMBER_COUNT
488+ || p -> survivor_node_id == p -> leaving_node_id
489+ || p -> nak != 0
490+ || p -> nak_reason != CLUSTER_LEAVE_NAK_NONE
491+ || p -> leave_epoch != 0
492+ || p -> leave_nonce == 0
493+ || p -> leave_nonce == UINT64_MAX ))
494+ return false;
467495 return true;
468496}
497+
498+ /* ------------------------------------------------------------------
499+ * Stage 8 phase-1 coordinated full-cluster clean-stop policy.
500+ * ------------------------------------------------------------------ */
501+
502+ bool
503+ cluster_clean_leave_phase1_full_stop_plan_valid (
504+ const ClusterPhase1FullStopPlan * plan )
505+ {
506+ int i ;
507+
508+ if (plan == NULL || !plan -> valid || plan -> epoch != 0
509+ || plan -> attempt_nonce == 0 || plan -> attempt_nonce == UINT64_MAX
510+ || plan -> absolute_deadline_us == 0
511+ || plan -> absolute_deadline_us == UINT64_MAX
512+ || plan -> own_wal_started_at <= 0 )
513+ return false;
514+ for (i = 0 ; i < CLUSTER_PHASE1_FULL_STOP_MEMBER_COUNT ; i ++ ) {
515+ if (plan -> member_incarnations [i ] == 0
516+ || plan -> member_incarnations [i ] == UINT64_MAX )
517+ return false;
518+ }
519+ return true;
520+ }
521+
522+ bool
523+ cluster_clean_leave_phase1_full_stop_probe_accepts (
524+ uint8 producer_kind , bool preflight , int32 envelope_source_node ,
525+ int32 payload_leaving_node , uint64 envelope_epoch , uint64 payload_epoch ,
526+ uint64 attempt_nonce , bool local_fast_shutdown , bool exact_phase1_eligible )
527+ {
528+ return producer_kind == CLUSTER_LEAVE_PRODUCER_SHUTDOWN
529+ && preflight
530+ && envelope_source_node >= 0
531+ && envelope_source_node < CLUSTER_PHASE1_FULL_STOP_MEMBER_COUNT
532+ && envelope_source_node == payload_leaving_node
533+ && envelope_epoch == payload_epoch
534+ && payload_epoch == 0
535+ && attempt_nonce != 0 && attempt_nonce != UINT64_MAX
536+ && local_fast_shutdown && exact_phase1_eligible ;
537+ }
538+
539+ bool
540+ cluster_clean_leave_phase1_full_stop_probe_phase_accepts (
541+ bool source_active , bool source_stopped ,
542+ bool local_active , bool local_stopped )
543+ {
544+ /* The two rounds deliberately reuse one wire shape. Existing source
545+ * WAL/root evidence is therefore the phase discriminator: an ACTIVE source
546+ * is still in the first round, while STOPPED+CLOSED is the second round.
547+ * A receiver may already have closed while helping a slower first-round
548+ * peer, but it must never ACK a second-round peer before its own close. */
549+ if (source_active == source_stopped
550+ || local_active == local_stopped )
551+ return false;
552+ if (source_stopped )
553+ return local_stopped ;
554+ return source_active ;
555+ }
556+
557+ bool
558+ cluster_clean_leave_phase1_full_stop_post_stopped_receiver_ready (
559+ bool local_stopped , bool request_in_progress , bool shutdown_driven ,
560+ bool preflight_pending , bool preflight_sent , bool release_pending )
561+ {
562+ /* The STOPPED after-image becomes an acknowledged local terminal only
563+ * after its publisher has confirmed the CF release and armed this process's
564+ * second round. Requiring the existing dispatch edge as well prevents a
565+ * peer from exiting before it has had a chance to receive our request. A
566+ * locally completed barrier remains the same exact round while its bounded
567+ * release/completion phase is active; accepting a slower peer's replay in
568+ * that interval prevents a one-way completion race without reopening the
569+ * round or refreshing its deadline. */
570+ return local_stopped && request_in_progress && shutdown_driven
571+ && preflight_sent && (preflight_pending || release_pending );
572+ }
573+
574+ bool
575+ cluster_clean_leave_phase1_full_stop_release_probe_accepts (
576+ uint8 producer_kind , uint8 wire_round , int32 envelope_source_node ,
577+ int32 payload_leaving_node , uint64 envelope_epoch , uint64 payload_epoch ,
578+ uint64 nonce , bool source_stopped , bool local_stopped ,
579+ bool exact_phase1_eligible )
580+ {
581+ return producer_kind == CLUSTER_LEAVE_PRODUCER_SHUTDOWN
582+ && wire_round == CLUSTER_PHASE1_FULL_STOP_WIRE_RELEASE
583+ && envelope_source_node >= 0
584+ && envelope_source_node < CLUSTER_PHASE1_FULL_STOP_MEMBER_COUNT
585+ && envelope_source_node == payload_leaving_node
586+ && envelope_epoch == payload_epoch
587+ && payload_epoch == 0
588+ && nonce != 0 && nonce != UINT64_MAX
589+ && source_stopped && local_stopped && exact_phase1_eligible ;
590+ }
591+
592+ bool
593+ cluster_clean_leave_phase1_full_stop_receipt_accepts (
594+ uint8 producer_kind , uint8 wire_round , bool release_pending ,
595+ uint64 stored_nonce , uint64 receipt_nonce , bool request_seen )
596+ {
597+ /* The exact receipt is peer-consumption evidence for a reply whose local
598+ * reply_sent publication may still be catching up after dispatch. Do not
599+ * require that local bookkeeping bit here: the terminal predicate checks it
600+ * independently together with every other delivery bit and transport drain. */
601+ return producer_kind == CLUSTER_LEAVE_PRODUCER_SHUTDOWN
602+ && wire_round == CLUSTER_PHASE1_FULL_STOP_WIRE_RECEIPT
603+ && release_pending
604+ && stored_nonce != 0 && stored_nonce != UINT64_MAX
605+ && receipt_nonce == stored_nonce
606+ && request_seen ;
607+ }
608+
609+ bool
610+ cluster_clean_leave_phase1_full_stop_release_complete (
611+ const ClusterPhase1FullStopPlan * plan , int32 self_node ,
612+ const uint8 * request_sent , const uint8 * request_seen ,
613+ const uint8 * reply_sent , const uint8 * reply_seen ,
614+ const uint8 * receipt_sent , const uint8 * receipt_seen ,
615+ int nbytes , bool transport_drained )
616+ {
617+ return transport_drained
618+ && cluster_clean_leave_phase1_full_stop_ack_complete (
619+ plan , self_node , request_sent , nbytes )
620+ && cluster_clean_leave_phase1_full_stop_ack_complete (
621+ plan , self_node , request_seen , nbytes )
622+ && cluster_clean_leave_phase1_full_stop_ack_complete (
623+ plan , self_node , reply_sent , nbytes )
624+ && cluster_clean_leave_phase1_full_stop_ack_complete (
625+ plan , self_node , reply_seen , nbytes )
626+ && cluster_clean_leave_phase1_full_stop_ack_complete (
627+ plan , self_node , receipt_sent , nbytes )
628+ && cluster_clean_leave_phase1_full_stop_ack_complete (
629+ plan , self_node , receipt_seen , nbytes );
630+ }
631+
632+ bool
633+ cluster_clean_leave_phase1_full_stop_request_ahead_can_consume (
634+ bool retained , bool retained_before_local_round ,
635+ uint64 retained_local_nonce , uint64 retained_deadline_us ,
636+ uint64 current_local_nonce , uint64 current_deadline_us ,
637+ bool exact_identity , bool request_in_progress ,
638+ bool shutdown_driven , bool post_requests_sent )
639+ {
640+ if (!retained || !exact_identity || !request_in_progress
641+ || !shutdown_driven || !post_requests_sent
642+ || retained_local_nonce == 0
643+ || retained_local_nonce == UINT64_MAX
644+ || retained_deadline_us == 0
645+ || retained_deadline_us == UINT64_MAX
646+ || current_local_nonce == 0
647+ || current_local_nonce == UINT64_MAX
648+ || current_deadline_us != retained_deadline_us )
649+ return false;
650+ if (retained_before_local_round )
651+ return cluster_clean_leave_phase1_full_stop_nonce_fresh (
652+ retained_local_nonce , current_local_nonce );
653+ return current_local_nonce == retained_local_nonce ;
654+ }
655+
656+ bool
657+ cluster_clean_leave_phase1_full_stop_request_ahead_uses_predecessor_nonce (
658+ bool local_active , bool local_stopped , bool local_post_round_armed )
659+ {
660+ /* WAL STOPPED is published before the checkpointer freezes the fresh
661+ * post-STOPPED nonce. That interval still belongs to the predecessor
662+ * local round even though the physical WAL after-image is already STOPPED.
663+ * Once the post round is armed, an early peer request binds the current
664+ * nonce and must not authorize another transition. */
665+ if (local_active == local_stopped )
666+ return false;
667+ return local_active || (local_stopped && !local_post_round_armed );
668+ }
669+
670+ bool
671+ cluster_clean_leave_phase1_full_stop_ack_matches (
672+ const ClusterPhase1FullStopPlan * plan , int32 self_node ,
673+ int32 envelope_source_node , int32 survivor_node , int32 leaving_node ,
674+ uint64 payload_epoch , uint64 attempt_nonce ,
675+ uint64 current_survivor_incarnation )
676+ {
677+ return cluster_clean_leave_phase1_full_stop_plan_valid (plan )
678+ && self_node >= 0
679+ && self_node < CLUSTER_PHASE1_FULL_STOP_MEMBER_COUNT
680+ && survivor_node >= 0
681+ && survivor_node < CLUSTER_PHASE1_FULL_STOP_MEMBER_COUNT
682+ && survivor_node != self_node
683+ && envelope_source_node == survivor_node
684+ && leaving_node == self_node
685+ && payload_epoch == plan -> epoch
686+ && attempt_nonce == plan -> attempt_nonce
687+ && current_survivor_incarnation
688+ == plan -> member_incarnations [survivor_node ];
689+ }
690+
691+ bool
692+ cluster_clean_leave_phase1_full_stop_ack_complete (
693+ const ClusterPhase1FullStopPlan * plan , int32 self_node ,
694+ const uint8 * ack_bitmap , int nbytes )
695+ {
696+ uint8 expected ;
697+ int i ;
698+
699+ if (!cluster_clean_leave_phase1_full_stop_plan_valid (plan )
700+ || self_node < 0
701+ || self_node >= CLUSTER_PHASE1_FULL_STOP_MEMBER_COUNT
702+ || ack_bitmap == NULL
703+ || nbytes != CLUSTER_CLEAN_LEAVE_ACK_BITMAP_BYTES )
704+ return false;
705+ expected = (uint8 )(UINT8_C (0x0f ) & ~(UINT8_C (1 ) << self_node ));
706+ if (ack_bitmap [0 ] != expected )
707+ return false;
708+ for (i = 1 ; i < nbytes ; i ++ ) {
709+ if (ack_bitmap [i ] != 0 )
710+ return false;
711+ }
712+ return true;
713+ }
714+
715+ bool
716+ cluster_clean_leave_phase1_full_stop_nonce_fresh (uint64 prior_nonce ,
717+ uint64 next_nonce )
718+ {
719+ return prior_nonce != 0 && prior_nonce != UINT64_MAX
720+ && next_nonce != 0 && next_nonce != UINT64_MAX
721+ && next_nonce != prior_nonce ;
722+ }
723+
724+ ClusterPhase1FullStopProbeNonceDecision
725+ cluster_clean_leave_phase1_full_stop_probe_nonce_decide (
726+ uint64 active_nonce , uint64 stopped_nonce , uint64 incoming_nonce )
727+ {
728+ if (active_nonce == 0 || active_nonce == UINT64_MAX
729+ || stopped_nonce == UINT64_MAX
730+ || incoming_nonce == 0 || incoming_nonce == UINT64_MAX )
731+ return CLUSTER_PHASE1_PROBE_NONCE_CONFLICT ;
732+ if (incoming_nonce == active_nonce )
733+ return CLUSTER_PHASE1_PROBE_NONCE_STALE_ACTIVE ;
734+ if (stopped_nonce == 0 )
735+ return CLUSTER_PHASE1_PROBE_NONCE_ACCEPT_STOPPED ;
736+ if (incoming_nonce == stopped_nonce )
737+ return CLUSTER_PHASE1_PROBE_NONCE_DUPLICATE_STOPPED ;
738+ return CLUSTER_PHASE1_PROBE_NONCE_CONFLICT ;
739+ }
0 commit comments