krabka_remote_storage_topic/manager/
readiness.rs1use {
9 super::{
10 TopicBasedRemoteLogMetadataManager,
11 assignment::{HWM_UNKNOWN, ReadGate, read_gate},
12 },
13 std::sync::PoisonError,
14};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub struct MetadataPartitionReadiness {
21 pub partition: i32,
23 pub target_offset: Option<i64>,
29 pub applied_offset: i64,
32 pub ready: bool,
35}
36
37impl TopicBasedRemoteLogMetadataManager {
38 #[must_use]
45 pub fn metadata_partition_readiness(&self) -> Vec<MetadataPartitionReadiness> {
46 let mut targets: Vec<(i32, i64)> = self
47 .ready_targets
48 .lock()
49 .unwrap_or_else(PoisonError::into_inner)
50 .iter()
51 .map(|(&partition, &target)| (partition, target))
52 .collect();
53 targets.sort_unstable();
54 targets
55 .into_iter()
56 .map(|(partition, target)| {
57 let applied = self.applied_offset(partition);
60 MetadataPartitionReadiness {
61 partition,
62 target_offset: (target != HWM_UNKNOWN).then_some(target),
63 applied_offset: applied.unwrap_or(-1),
64 ready: matches!(read_gate(target, applied), ReadGate::Ready),
65 }
66 })
67 .collect()
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use {
74 super::{HWM_UNKNOWN, MetadataPartitionReadiness},
75 crate::{
76 log::{InProcessMetadataEventLog, MetadataEventLog as _},
77 manager::test_support::{
78 HwmFlakyLog, SeededManager, seeded_manager, start_manager, tp, wait_ready,
79 },
80 },
81 assert2::{assert, check},
82 std::{collections::HashMap, sync::Arc},
83 };
84
85 struct Case {
88 name: &'static str,
89 targets: &'static [(i32, i64)],
90 applied: [i64; 4],
91 expected: Vec<MetadataPartitionReadiness>,
92 }
93
94 const fn readiness(
95 partition: i32,
96 target_offset: Option<i64>,
97 applied_offset: i64,
98 ready: bool,
99 ) -> MetadataPartitionReadiness {
100 MetadataPartitionReadiness {
101 partition,
102 target_offset,
103 applied_offset,
104 ready,
105 }
106 }
107
108 #[tokio::test(flavor = "multi_thread")]
109 async fn readiness_reports_each_assigned_partition_against_its_gate() {
110 let cases = [
111 Case {
112 name: "unassigned",
113 targets: &[],
114 applied: [5, 5, 5, 5],
115 expected: vec![],
116 },
117 Case {
118 name: "assigned empty partition, target 0",
119 targets: &[(2, 0)],
120 applied: [-1, -1, -1, -1],
121 expected: vec![readiness(2, Some(0), -1, true)],
122 },
123 Case {
124 name: "assigned behind",
125 targets: &[(1, 10)],
126 applied: [-1, 7, -1, -1],
127 expected: vec![readiness(1, Some(10), 7, false)],
128 },
129 Case {
130 name: "assigned caught up",
131 targets: &[(1, 10)],
132 applied: [-1, 9, -1, -1],
133 expected: vec![readiness(1, Some(10), 9, true)],
134 },
135 Case {
136 name: "HWM unknown",
137 targets: &[(3, HWM_UNKNOWN)],
138 applied: [-1, -1, -1, 4],
139 expected: vec![readiness(3, None, 4, false)],
140 },
141 Case {
142 name: "several partitions, sorted",
143 targets: &[(3, 2), (0, HWM_UNKNOWN), (2, 0)],
144 applied: [-1, -1, -1, 0],
145 expected: vec![
146 readiness(0, None, -1, false),
147 readiness(2, Some(0), -1, true),
148 readiness(3, Some(2), 0, false),
149 ],
150 },
151 ];
152 for Case {
153 name,
154 targets,
155 applied,
156 expected,
157 } in cases
158 {
159 let manager = start_manager(InProcessMetadataEventLog::new(4));
162 *manager.ready_targets.lock().unwrap() =
163 targets.iter().copied().collect::<HashMap<_, _>>();
164 *manager.applied.lock().unwrap() = applied.to_vec();
165 check!(manager.metadata_partition_readiness() == expected, "{name}");
166 manager.shutdown();
167 }
168 }
169
170 #[tokio::test(flavor = "multi_thread")]
173 async fn readiness_follows_a_failed_then_refreshed_high_water_mark() {
174 let log = HwmFlakyLog::new(4);
175 let SeededManager { manager, partition } =
176 seeded_manager(Arc::<HwmFlakyLog>::clone(&log)).await;
177 let hwm = log.high_water_marks().await.unwrap()[usize::try_from(partition).unwrap()];
178 assert!(hwm > 0);
179
180 log.set_fail_hwm(true);
181 manager.reconcile_assignment(&[partition]).await;
182 let unknown = manager.metadata_partition_readiness();
185 let applied = unknown.first().map_or(-1, |entry| entry.applied_offset);
186 check!(unknown == [readiness(partition, None, applied, false)]);
187
188 log.set_fail_hwm(false);
189 manager.reconcile_assignment(&[partition]).await;
190 wait_ready(&manager, &tp()).await;
191 check!(
192 manager.metadata_partition_readiness()
193 == [readiness(partition, Some(hwm), hwm - 1, true)]
194 );
195
196 manager.reconcile_assignment(&[]).await;
197 check!(manager.metadata_partition_readiness().is_empty());
198 manager.shutdown();
199 }
200}