krabka_client_consumer/
assignment.rs1use std::{
13 collections::{BTreeSet, HashSet},
14 sync::atomic::{AtomicU64, Ordering},
15};
16
17use crate::{consumer::Consumer, error::ConsumerError, poll::COMMITTED_SENTINEL};
18
19static MANUAL_OWNERSHIP_IDS: AtomicU64 = AtomicU64::new(1 << 62);
23
24impl Consumer {
25 pub async fn assign(&self, partitions: &[(String, i32)]) -> Result<(), ConsumerError> {
38 if partitions.iter().any(|(topic, _)| topic.is_empty()) {
39 return Err(ConsumerError::InvalidArgument(
40 "Topic partitions to assign to cannot have null or empty topic".to_owned(),
41 ));
42 }
43 if let Some((topic, partition)) = partitions.iter().find(|(_, partition)| *partition < 0) {
44 return Err(ConsumerError::InvalidArgument(format!(
45 "Partition index of {topic}-{partition} is negative"
46 )));
47 }
48 if !self.subscription.borrow().is_none() {
49 return Err(ConsumerError::IllegalState(
50 "Subscription to topics, partitions and pattern are mutually exclusive".to_owned(),
51 ));
52 }
53 let wanted: Vec<(String, i32)> = partitions
54 .iter()
55 .cloned()
56 .collect::<BTreeSet<_>>()
57 .into_iter()
58 .collect();
59 let wanted_set: HashSet<(String, i32)> = wanted.iter().cloned().collect();
60 let added: Vec<(String, i32)> = {
61 let mut assigned = self.assigned.lock().await;
64 let mut identity = self.commit_identity.lock().await;
65 let added = wanted
66 .iter()
67 .filter(|partition| !assigned.contains(*partition))
68 .cloned()
69 .collect();
70 identity
71 .ownership_ids
72 .retain(|partition, _| wanted_set.contains(partition));
73 for partition in &wanted {
74 identity
75 .ownership_ids
76 .entry(partition.clone())
77 .or_insert_with(|| MANUAL_OWNERSHIP_IDS.fetch_add(1, Ordering::Relaxed));
78 }
79 identity.generation = -1;
80 identity.member_id.clear();
81 identity.rejoin_on_poll = false;
82 assigned.clone_from(&wanted);
83 added
84 };
85 {
86 let mut offsets = self.next_offsets.lock().await;
87 let mut positions = self.positions.lock().await;
88 offsets.retain(|partition, _| wanted_set.contains(partition));
89 positions.retain(|partition, _| wanted_set.contains(partition));
90 for partition in added {
91 offsets.insert(partition.clone(), COMMITTED_SENTINEL);
92 positions.entry(partition).or_default();
93 }
94 }
95 self.end_offsets
96 .lock()
97 .await
98 .retain(|partition, _| wanted_set.contains(partition));
99 self.current_generation.store(-1, Ordering::Release);
100 self.client.metadata_topics().set(
103 wanted
104 .iter()
105 .map(|(topic, _)| topic.clone())
106 .collect::<BTreeSet<_>>(),
107 );
108 self.subscription.send_if_modified(|subscription| {
109 let manual = !wanted.is_empty();
110 let changed = subscription.manual_assignment != manual;
111 subscription.manual_assignment = manual;
112 changed
113 });
114 self.assignment_changed.notify_waiters();
115 Ok(())
116 }
117
118 pub(crate) fn require_group_id(&self) -> Result<(), ConsumerError> {
121 if self.group_id.is_empty() {
122 return Err(ConsumerError::InvalidGroupId);
123 }
124 Ok(())
125 }
126
127 pub(crate) fn require_group_membership(&self) -> Result<(), ConsumerError> {
129 self.require_group_id()?;
130 if self.coordinator_handle.is_none() {
131 return Err(ConsumerError::IllegalState(
132 "the consumer was built without a subscription, so it has no group membership; build it with subscribe or subscribe_pattern to join a group".to_owned(),
133 ));
134 }
135 Ok(())
136 }
137}