krabka_verified/isr/
watermark.rs1use {
2 super::{
3 HighWatermarkFacts, HwmReplica, IsrCandidateFacts, IsrEligibilityFacts, IsrMemberRole,
4 },
5 creusot_std::prelude::*,
6};
7
8#[cfg(creusot)]
13#[cfg_attr(test, mutants::skip)]
14#[logic]
15fn kafka_is_replica_isr_eligible(facts: IsrEligibilityFacts) -> bool {
16 pearlite! {
17 !facts.fenced && !facts.shutting_down
18 && match facts.fetch_broker_epoch {
19 Some(stored) => match facts.alive_broker_epoch {
20 Some(alive) => stored@ == -1 || stored@ == alive@,
21 None => false,
22 },
23 None => false,
24 }
25 }
26}
27
28#[cfg(creusot)]
34#[cfg_attr(test, mutants::skip)]
35#[logic]
36fn kafka_replica_is_caught_up(
37 follower_log_end: Int,
38 leader_log_end: Int,
39 caught_up_within_lag: bool,
40) -> bool {
41 pearlite! { follower_log_end == leader_log_end || caught_up_within_lag }
42}
43
44#[ensures(result == kafka_is_replica_isr_eligible(facts))]
47#[must_use]
48pub fn replica_isr_eligible(facts: IsrEligibilityFacts) -> bool {
49 if facts.fenced || facts.shutting_down {
50 return false;
51 }
52 match (facts.fetch_broker_epoch, facts.alive_broker_epoch) {
53 (Some(stored), Some(alive)) => stored == -1 || stored == alive,
54 _ => false,
55 }
56}
57
58#[cfg(creusot)]
65#[cfg_attr(test, mutants::skip)]
66#[logic]
67fn kafka_is_follower_in_sync(facts: IsrCandidateFacts) -> bool {
68 pearlite! {
69 facts.follower_log_end@ >= facts.leader_high_watermark@
70 && match facts.leader_epoch_start {
71 Some(start) => facts.follower_log_end@ >= start@,
72 None => false,
73 }
74 }
75}
76
77#[ensures(match facts.role {
87 IsrMemberRole::Unassigned => !result,
88 IsrMemberRole::Leader => result,
89 IsrMemberRole::InSyncFollower => result == kafka_replica_is_caught_up(
90 facts.follower_log_end@, facts.leader_log_end@, facts.caught_up_within_lag),
91 IsrMemberRole::OutOfSyncFollower => result == (
92 kafka_is_replica_isr_eligible(facts.eligibility) && kafka_is_follower_in_sync(facts)),
93})]
94#[must_use]
95pub fn isr_candidate_selected(facts: IsrCandidateFacts) -> bool {
96 match facts.role {
97 IsrMemberRole::Unassigned => false,
98 IsrMemberRole::Leader => true,
99 IsrMemberRole::InSyncFollower => {
100 facts.follower_log_end == facts.leader_log_end || facts.caught_up_within_lag
101 }
102 IsrMemberRole::OutOfSyncFollower => {
103 let in_sync = facts.follower_log_end >= facts.leader_high_watermark
104 && matches!(
105 facts.leader_epoch_start,
106 Some(start) if facts.follower_log_end >= start
107 );
108 in_sync && replica_isr_eligible(facts.eligibility)
109 }
110 }
111}
112
113#[cfg(creusot)]
116#[cfg_attr(test, mutants::skip)]
117#[logic]
118fn kafka_is_under_min_isr(facts: HighWatermarkFacts) -> bool {
119 pearlite! { facts.isr_size@ < facts.effective_min_isr@ }
120}
121
122#[cfg(creusot)]
128#[cfg_attr(test, mutants::skip)]
129#[logic]
130fn kafka_holds_watermark(replica: HwmReplica, leader_log_end: Int) -> bool {
131 pearlite! {
132 replica.in_isr
133 || (kafka_replica_is_caught_up(replica.log_end@, leader_log_end, replica.caught_up_within_lag)
134 && kafka_is_replica_isr_eligible(replica.eligibility))
135 }
136}
137
138#[cfg(creusot)]
143#[cfg_attr(test, mutants::skip)]
144#[logic]
145#[variant(n)]
146fn kafka_watermark_candidate(leader_log_end: Int, replicas: Seq<HwmReplica>, n: Int) -> Int {
147 pearlite! {
148 if n <= 0 {
149 leader_log_end
150 } else {
151 if kafka_holds_watermark(replicas[n - 1], leader_log_end)
152 && replicas[n - 1].log_end@ < kafka_watermark_candidate(leader_log_end, replicas, n - 1)
153 {
154 replicas[n - 1].log_end@
155 } else {
156 kafka_watermark_candidate(leader_log_end, replicas, n - 1)
157 }
158 }
159 }
160}
161
162#[cfg(creusot)]
168#[cfg_attr(test, mutants::skip)]
169#[logic]
170fn kafka_leader_high_watermark(facts: HighWatermarkFacts, replicas: Seq<HwmReplica>) -> Int {
171 pearlite! {
172 if kafka_is_under_min_isr(facts) {
173 facts.current@
174 } else if kafka_watermark_candidate(facts.leader_log_end@, replicas, replicas.len())
175 > facts.current@
176 {
177 kafka_watermark_candidate(facts.leader_log_end@, replicas, replicas.len())
178 } else {
179 facts.current@
180 }
181 }
182}
183
184#[requires(facts.current@ <= facts.leader_log_end@)]
197#[ensures(result@ == kafka_leader_high_watermark(facts, replicas@))]
198#[ensures(facts.current@ <= result@ && result@ <= facts.leader_log_end@)]
199#[ensures(kafka_is_under_min_isr(facts) ==> result == facts.current)]
200#[must_use]
201pub fn leader_high_watermark(facts: HighWatermarkFacts, replicas: &[HwmReplica]) -> i64 {
202 if facts.isr_size < facts.effective_min_isr {
203 return facts.current;
204 }
205 let mut candidate = facts.leader_log_end;
206 let mut i = 0;
207 #[cfg_attr(creusot, invariant(i@ <= [email protected]()))]
208 #[cfg_attr(creusot, invariant(candidate@ <= facts.leader_log_end@))]
209 #[cfg_attr(creusot, invariant(candidate@
210 == kafka_watermark_candidate(facts.leader_log_end@, replicas@, i@)))]
211 #[cfg_attr(creusot, variant([email protected]() - i@))]
212 while i < replicas.len() {
213 let Some(&replica) = replicas.get(i) else {
214 break;
215 };
216 let caught_up = replica.log_end == facts.leader_log_end || replica.caught_up_within_lag;
217 let holds = replica.in_isr || (caught_up && replica_isr_eligible(replica.eligibility));
218 if holds && replica.log_end < candidate {
219 candidate = replica.log_end;
220 }
221 i += 1;
222 }
223 if candidate > facts.current {
224 candidate
225 } else {
226 facts.current
227 }
228}
229
230#[ensures(result == (removed@ > 0 || added@ > 0))]
232#[must_use]
233pub fn isr_proposal_changed(removed: usize, added: usize) -> bool { removed > 0 || added > 0 }