@@ -50,6 +50,7 @@ pub struct EventProcessor {
5050 next_partition_client_sender : Sender < Arc < PartitionClient > > ,
5151 client_details : ConsumerClientDetails ,
5252 prefetch : u32 ,
53+ owner_level : Option < i64 > ,
5354 update_interval : Duration ,
5455 start_positions : StartPositions ,
5556 is_running : std:: sync:: Mutex < bool > ,
@@ -62,6 +63,7 @@ struct EventProcessorOptions {
6263 update_interval : Duration ,
6364 start_positions : StartPositions ,
6465 prefetch : u32 ,
66+ owner_level : Option < i64 > ,
6567 partition_ids : Vec < String > ,
6668}
6769
@@ -123,6 +125,38 @@ impl ProcessorConsumersMap {
123125 info ! ( "Consumers for partition now: {:?}" , consumers. keys( ) ) ;
124126 Ok ( ( ) )
125127 }
128+
129+ /// Returns the set of partition IDs that have active partition clients.
130+ fn get_active_partition_ids ( & self ) -> Result < Vec < String > > {
131+ let consumers = self
132+ . consumers
133+ . lock ( )
134+ . map_err ( |_| EventHubsError :: with_message ( "Could not lock consumers mutex." ) ) ?;
135+ Ok ( consumers. keys ( ) . cloned ( ) . collect ( ) )
136+ }
137+
138+ /// Revokes and removes partition clients for partitions that are no longer
139+ /// owned by this processor instance.
140+ ///
141+ /// This is called during dispatch when the load balancer indicates that
142+ /// a partition has been reassigned to another consumer. The partition client
143+ /// is marked as revoked (so the consumer can detect it via `is_revoked()`)
144+ /// and removed from the consumers map (so a new client can be created if the
145+ /// partition is later reassigned back).
146+ fn revoke_partition_clients ( & self , partition_ids : & [ String ] ) -> Result < ( ) > {
147+ let mut consumers = self
148+ . consumers
149+ . lock ( )
150+ . map_err ( |_| EventHubsError :: with_message ( "Could not lock consumers mutex." ) ) ?;
151+ for partition_id in partition_ids {
152+ if let Some ( weak) = consumers. remove ( partition_id) {
153+ if let Some ( client) = weak. upgrade ( ) {
154+ client. revoke ( ) ;
155+ }
156+ }
157+ }
158+ Ok ( ( ) )
159+ }
126160}
127161
128162//pub(crate) type ConsumersType = std::sync::Mutex<HashMap<String, Arc<PartitionClient>>>;
@@ -163,6 +197,7 @@ impl EventProcessor {
163197 ) ) ) ,
164198 client_details,
165199 prefetch : options. prefetch ,
200+ owner_level : options. owner_level ,
166201 update_interval : options. update_interval ,
167202 start_positions : options. start_positions ,
168203 next_partition_client_sender : sender,
@@ -293,6 +328,25 @@ impl EventProcessor {
293328 e
294329 } ) ?;
295330
331+ // Detect partitions that were stolen by another consumer.
332+ // Compare the set of partitions we currently own (from load_balance)
333+ // against active partition clients. Revoke any clients for partitions
334+ // we no longer own so they stop processing.
335+ let owned_ids: std:: collections:: HashSet < & str > =
336+ ownerships. iter ( ) . map ( |o| o. partition_id . as_str ( ) ) . collect ( ) ;
337+ let active_ids = consumers. get_active_partition_ids ( ) ?;
338+ let stolen: Vec < String > = active_ids
339+ . into_iter ( )
340+ . filter ( |id| !owned_ids. contains ( id. as_str ( ) ) )
341+ . collect ( ) ;
342+ if !stolen. is_empty ( ) {
343+ info ! (
344+ "Partitions no longer owned, revoking: {}" ,
345+ stolen. join( ", " )
346+ ) ;
347+ consumers. revoke_partition_clients ( & stolen) ?;
348+ }
349+
296350 let checkpoints = self . get_checkpoint_map ( ) . await ;
297351 let checkpoints = checkpoints. map_err ( |e| {
298352 error ! ( "Error in getting checkpoint map: {:?}" , e) ;
@@ -366,6 +420,7 @@ impl EventProcessor {
366420 Some ( OpenReceiverOptions {
367421 start_position : Some ( start_position) ,
368422 prefetch : Some ( self . prefetch ) ,
423+ owner_level : self . owner_level ,
369424 ..Default :: default ( )
370425 } ) ,
371426 )
@@ -527,7 +582,7 @@ pub mod builders {
527582
528583 const DEFAULT_PREFETCH : u32 = 300 ;
529584 const DEFAULT_UPDATE_INTERVAL : Duration = Duration :: seconds ( 30 ) ;
530- const DEFAULT_PARTITION_EXPIRATION_DURATION : Duration = Duration :: seconds ( 10 ) ;
585+ const DEFAULT_PARTITION_EXPIRATION_DURATION : Duration = Duration :: seconds ( 60 ) ;
531586
532587 /// Builder for creating an `EventProcessor`.
533588 /// This builder allows you to configure various options for the event processor,
@@ -564,6 +619,7 @@ pub mod builders {
564619 start_positions : Option < StartPositions > ,
565620 max_partition_count : Option < usize > ,
566621 prefetch : Option < u32 > ,
622+ owner_level : Option < i64 > ,
567623 load_balancing_strategy : Option < super :: ProcessorStrategy > ,
568624 partition_expiration_duration : Option < Duration > ,
569625 }
@@ -611,6 +667,17 @@ pub mod builders {
611667 self
612668 }
613669
670+ /// Sets the owner level (epoch) for partition receivers.
671+ ///
672+ /// When set, the Event Hub broker enforces exclusive access: a new
673+ /// receiver with the same or higher owner level will disconnect any
674+ /// existing receiver on the same partition. This prevents duplicate
675+ /// processing when partitions are rebalanced across consumers.
676+ pub fn with_owner_level ( mut self , owner_level : i64 ) -> Self {
677+ self . owner_level = Some ( owner_level) ;
678+ self
679+ }
680+
614681 /// Sets the partition expiration duration for the event processor.
615682 pub fn with_partition_expiration_duration (
616683 mut self ,
@@ -647,6 +714,7 @@ pub mod builders {
647714 update_interval : self . update_interval . unwrap_or ( DEFAULT_UPDATE_INTERVAL ) ,
648715 start_positions : self . start_positions . unwrap_or_default ( ) ,
649716 prefetch : self . prefetch . unwrap_or ( DEFAULT_PREFETCH ) ,
717+ owner_level : self . owner_level ,
650718 partition_ids : eh_properties. partition_ids ,
651719 } ,
652720 )
0 commit comments