@@ -580,7 +580,9 @@ pub enum VsrAction {
580580 } ,
581581 /// Broadcast a `RequestStartView` probe (recovering replica asking for
582582 /// the current view's `StartView`; only that view's primary answers).
583- SendRequestStartView { namespace : u64 } ,
583+ /// Stamped with the prober's view so peers can fence stale duplicates
584+ /// out of the probed-primary election path.
585+ SendRequestStartView { view : u32 , namespace : u64 } ,
584586 /// Send `StartView` to all backups (as new primary).
585587 SendStartView {
586588 view : u32 ,
@@ -1236,6 +1238,7 @@ impl<B: MessageBus, P: Pipeline<Entry = PipelineEntry>> VsrConsensus<B, P> {
12361238 . borrow_mut ( )
12371239 . reset ( TimeoutKind :: RequestStartViewMessage ) ;
12381240 actions. push ( VsrAction :: SendRequestStartView {
1241+ view : self . view . get ( ) ,
12391242 namespace : self . namespace ,
12401243 } ) ;
12411244 }
@@ -1245,6 +1248,7 @@ impl<B: MessageBus, P: Pipeline<Entry = PipelineEntry>> VsrConsensus<B, P> {
12451248 . borrow_mut ( )
12461249 . reset ( TimeoutKind :: RequestStartViewMessage ) ;
12471250 actions. push ( VsrAction :: SendRequestStartView {
1251+ view : self . view . get ( ) ,
12481252 namespace : self . namespace ,
12491253 } ) ;
12501254 }
@@ -1845,24 +1849,24 @@ impl<B: MessageBus, P: Pipeline<Entry = PipelineEntry>> VsrConsensus<B, P> {
18451849 /// election's `StartView` adopts this replica first). The replica sits
18461850 /// in `Status::Recovering` meanwhile: it acks nothing, votes in no
18471851 /// election, and initiates nothing.
1848- pub fn begin_view_probe ( & self ) -> Vec < VsrAction > {
1852+ /// Returns nothing: the first probe rides the
1853+ /// `RequestStartViewMessage` timeout (~1s after boot), by which point
1854+ /// the replica mesh -- absent entirely at the boot-time call sites --
1855+ /// has formed. Emitting an action here implied a send that never
1856+ /// happened.
1857+ pub fn begin_view_probe ( & self ) {
18491858 tracing:: info!(
18501859 replica = self . replica,
18511860 namespace_raw = self . namespace,
18521861 "beginning view probe"
18531862 ) ;
18541863 self . status . set ( Status :: Recovering ) ;
18551864 self . probe_attempts . set ( 0 ) ;
1856- {
1857- let mut timeouts = self . timeouts . borrow_mut ( ) ;
1858- timeouts. stop ( TimeoutKind :: Prepare ) ;
1859- timeouts. stop ( TimeoutKind :: CommitMessage ) ;
1860- timeouts. stop ( TimeoutKind :: NormalHeartbeat ) ;
1861- timeouts. start ( TimeoutKind :: RequestStartViewMessage ) ;
1862- }
1863- vec ! [ VsrAction :: SendRequestStartView {
1864- namespace: self . namespace,
1865- } ]
1865+ let mut timeouts = self . timeouts . borrow_mut ( ) ;
1866+ timeouts. stop ( TimeoutKind :: Prepare ) ;
1867+ timeouts. stop ( TimeoutKind :: CommitMessage ) ;
1868+ timeouts. stop ( TimeoutKind :: NormalHeartbeat ) ;
1869+ timeouts. start ( TimeoutKind :: RequestStartViewMessage ) ;
18661870 }
18671871
18681872 /// Peer side of the probe (sent by a Recovering replica at boot, or by
@@ -1893,7 +1897,17 @@ impl<B: MessageBus, P: Pipeline<Entry = PipelineEntry>> VsrConsensus<B, P> {
18931897 return Vec :: new ( ) ;
18941898 }
18951899 if self . primary_index ( self . view . get ( ) ) == header. replica {
1896- return self . start_election ( plane, ViewChangeReason :: PrimaryProbedView ) ;
1900+ // Probes are re-broadcast on a timer, so delayed duplicates are
1901+ // the normal case, and `primary_index` is view % replica_count:
1902+ // a stale probe from replica R re-matches every replica_count
1903+ // views. Only a probe stamped with the CURRENT view proves the
1904+ // current primary is the one probing; anything else falls
1905+ // through (a backup answers nothing, and the true primary of a
1906+ // newer view answers with its StartView).
1907+ if header. view == self . view . get ( ) {
1908+ return self . start_election ( plane, ViewChangeReason :: PrimaryProbedView ) ;
1909+ }
1910+ return Vec :: new ( ) ;
18971911 }
18981912 if !self . is_primary ( ) || self . ceded_primaryship . get ( ) {
18991913 return Vec :: new ( ) ;
0 commit comments