1use std::collections::BTreeMap;
13use std::num::{NonZeroU64, NonZeroUsize};
14
15use haematite::SyncCounts;
16use haematite::branch::history::Row;
17use haematite::branch::{CommitDurability, CommitRequest, KeptHead, Timestamp};
18use haematite::db::ReadOnlyDatabase;
19use haematite::tree::Hash;
20use serde::Serialize;
21
22use super::branches::provenance;
23use super::dead;
24use super::embedded;
25use super::history::{self, Decision, Released};
26use super::{
27 BRANCH_PREFIX, BranchRefRecord, Captured, Failure, SERVICE, Storage, branch_name, identifier,
28 open_branch, version,
29};
30use crate::serve::protocol::Version;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
34#[serde(rename_all = "snake_case")]
35pub enum Source {
36 Head,
37 Fork,
38 Merge,
39 Discard,
40 Embed,
43 EmbedOf,
46 KeepVersions,
48 KeepDays,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
54pub struct HeldBy {
55 pub source: Source,
56 pub name: String,
58 pub version: Version,
59}
60
61#[derive(Debug, Clone, Default, PartialEq, Eq)]
63pub struct Floor {
64 pub held_by: Vec<HeldBy>,
68 pub branches_read: usize,
70}
71
72impl Floor {
73 pub fn oldest(&self) -> Option<&HeldBy> {
77 self.held_by.first()
78 }
79
80 pub fn oldest_in(&self, generation: u64) -> Option<Version> {
84 self.held_by
85 .iter()
86 .map(|held| held.version)
87 .filter(|version| version.generation == generation)
88 .min_by_key(|version| order(*version))
89 }
90}
91
92pub struct IntentListing;
95
96impl PendingDiscards for IntentListing {
97 fn pending(&self, storage: &Storage) -> Result<Vec<(String, Version)>, Failure> {
98 storage.pending_discards()
99 }
100}
101
102pub trait PendingDiscards {
104 fn pending(&self, storage: &Storage) -> Result<Vec<(String, Version)>, Failure>;
107}
108
109#[cfg(test)]
114pub struct DiscardsUnread;
115
116#[cfg(test)]
117impl PendingDiscards for DiscardsUnread {
118 fn pending(&self, _storage: &Storage) -> Result<Vec<(String, Version)>, Failure> {
119 Err(Failure::new(
120 "release_discards_unread",
121 "the pending discards cannot be read yet, so nothing is released",
122 ))
123 }
124}
125
126impl Storage {
127 pub fn relied_on(
134 &self,
135 branch: &str,
136 discards: &dyn PendingDiscards,
137 ) -> Result<Floor, Failure> {
138 identifier(branch)?;
139 let head = self
140 .refs
141 .get(&branch_name(branch))
142 .ok_or_else(|| Failure::new("branch_missing", branch))?;
143 let head = HeldBy {
144 source: Source::Head,
145 name: branch.to_owned(),
146 version: version(head),
147 };
148 let mut floor = self.relied_on_by_others(branch, false, discards)?;
149 floor.held_by.push(head);
150 floor
151 .held_by
152 .sort_by_key(|held| (held.version.generation, held.version.sequence));
153 Ok(floor)
154 }
155
156 pub(super) fn relied_on_by_others(
168 &self,
169 branch: &str,
170 skip_own_discard: bool,
171 discards: &dyn PendingDiscards,
172 ) -> Result<Floor, Failure> {
173 identifier(branch)?;
174 let mut held_by = Vec::new();
175 let mut branches_read = 0_usize;
176 for record in self.refs.list_prefix(BRANCH_PREFIX) {
177 let Some(fork) = record.name.strip_prefix(BRANCH_PREFIX) else {
178 continue;
179 };
180 if fork == branch {
181 continue;
182 }
183 let at = Captured {
184 branch: fork.to_owned(),
185 version: version(record),
186 shards: record.shards.clone(),
187 };
188 branches_read = branches_read.saturating_add(1);
189 if let Some(origin) = provenance(&self.reader, &at)?
190 && origin.branch == branch
191 {
192 held_by.push(HeldBy {
193 source: Source::Fork,
194 name: fork.to_owned(),
195 version: origin.version,
196 });
197 }
198 }
199 for plan in self.open_merge_plans()? {
200 for pin in [&plan.origin, &plan.source, &plan.target] {
201 if pin.branch == branch {
202 held_by.push(HeldBy {
203 source: Source::Merge,
204 name: plan.id.clone(),
205 version: pin.version,
206 });
207 }
208 }
209 }
210 for (discarded, at) in discards.pending(self)? {
213 if discarded == branch && !skip_own_discard {
214 held_by.push(HeldBy {
215 source: Source::Discard,
216 name: discarded,
217 version: at,
218 });
219 }
220 }
221 held_by.extend(self.embed_positions(branch)?);
222 held_by.sort_by_key(|held| (held.version.generation, held.version.sequence));
223 Ok(Floor {
224 held_by,
225 branches_read,
226 })
227 }
228}
229
230impl Storage {
231 fn embed_positions(&self, branch: &str) -> Result<Vec<HeldBy>, Failure> {
240 let mut held = Vec::new();
241 for record in self.refs.list_prefix(embedded::COMPANION_PREFIX) {
242 let Some(owner) = record.name.strip_prefix(embedded::COMPANION_PREFIX) else {
243 continue;
244 };
245 for ((collection, field), position) in self.companion_positions(record)? {
246 let field = format!("{collection}.{field}");
247 if position.branch == branch
249 && let Some(through) = position.through
250 {
251 let (source, name) = if owner == branch {
252 (Source::Embed, field.clone())
253 } else {
254 (Source::EmbedOf, format!("{owner}:{field}"))
255 };
256 held.push(HeldBy {
257 source,
258 name,
259 version: through,
260 });
261 }
262 if owner == branch
265 && let Some(open) = position.toward
266 {
267 held.push(HeldBy {
268 source: Source::Embed,
269 name: field,
270 version: open.version,
271 });
272 }
273 }
274 }
275 Ok(held)
276 }
277
278 pub(super) fn companion_positions(
286 &self,
287 companion: &BranchRefRecord,
288 ) -> Result<BTreeMap<(String, String), embedded::Position>, Failure> {
289 let limit = NonZeroUsize::new(POSITIONS_A_PAGE)
290 .ok_or_else(|| Failure::new("embed_positions_page", "a page must be nonzero"))?;
291 let mut positions = BTreeMap::new();
292 for shard in &companion.shards {
293 let mut from = b"e\0".to_vec();
294 loop {
295 let page = self
296 .reader
297 .range_page_at(shard.shard_id, shard.head, &from, b"e\x01", limit)
298 .map_err(Failure::observer)?;
299 for (key, bytes) in page.entries {
300 let field = position_field(&companion.name, &key)?;
301 positions.insert(field, embedded::read_position(&bytes)?);
302 }
303 let Some(next) = page.next_from else {
304 break;
305 };
306 from = next;
307 }
308 }
309 Ok(positions)
310 }
311}
312
313fn position_field(companion: &str, key: &[u8]) -> Result<(String, String), Failure> {
317 let corrupt = || {
318 Failure::new(
319 "embed_position_key_corrupt",
320 format!("{companion} holds a position key that names no collection and field"),
321 )
322 };
323 let named = key.strip_prefix(b"e\0".as_slice()).ok_or_else(corrupt)?;
324 let named = std::str::from_utf8(named).map_err(|_error| corrupt())?;
325 let (collection, field) = named.split_once('\0').ok_or_else(corrupt)?;
326 Ok((collection.to_owned(), field.to_owned()))
327}
328
329const POSITIONS_A_PAGE: usize = 64;
331
332#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
336pub struct Keep {
337 pub versions: Option<NonZeroU64>,
339 pub days: Option<u64>,
341}
342
343#[derive(Debug, Clone, Copy, PartialEq, Eq)]
345pub struct Rules {
346 pub keep: Option<Keep>,
350 pub max_rows: NonZeroUsize,
352 pub now: Timestamp,
354}
355
356#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
358pub struct Span {
359 pub first: Version,
360 pub last: Version,
361}
362
363#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
365pub struct Answer {
366 pub branch: String,
367 pub released: Option<Span>,
368 pub marker: Option<Released>,
369 pub held_by: Vec<HeldBy>,
371 pub more: bool,
373 pub branches_read: usize,
374 pub pass_asked: bool,
376 pub pass_refused: Option<String>,
378 pub due_at: Option<Timestamp>,
382}
383
384const NANOS_A_DAY: u64 = 86_400 * 1_000_000_000;
385
386const LOWEST: Version = Version {
388 generation: 0,
389 sequence: 0,
390};
391
392const fn order(version: Version) -> (u64, u64) {
393 (version.generation, version.sequence)
394}
395
396const fn row_version(row: &Row) -> Version {
397 Version {
398 generation: row.key.generation,
399 sequence: row.key.sequence,
400 }
401}
402
403#[derive(Debug, Clone, Copy, PartialEq, Eq)]
407pub struct Below {
408 pub version: Version,
409 pub written: Timestamp,
410}
411
412fn judge(
425 branch: &str,
426 rows: &[Row],
427 cap: usize,
428 floor: &Floor,
429 below: Below,
430 rules: &Rules,
431) -> (Vec<Version>, Vec<HeldBy>, bool) {
432 let mut taken = Vec::new();
433 let mut passed = Vec::new();
434 for (index, row) in rows.iter().enumerate() {
435 let at = row_version(row);
436 if order(at) >= order(below.version) {
437 break;
438 }
439 let held = held_at(branch, rows, index, floor, below, rules);
440 if index == cap {
441 let more = held.is_empty() && !taken.is_empty();
444 let held_by = if taken.is_empty() { passed } else { Vec::new() };
445 return (taken, held_by, more);
446 }
447 if !held.is_empty() {
448 if taken.is_empty() && floor_holds(floor, at) {
449 if passed.is_empty() {
450 passed = held;
451 }
452 continue;
453 }
454 return (taken, held, false);
455 }
456 taken.push(at);
457 }
458 let held_by = if taken.is_empty() { passed } else { Vec::new() };
459 (taken, held_by, false)
460}
461
462#[derive(Debug, Clone, Copy)]
465struct ReadFrom {
466 from: Version,
467 below: Below,
468 limit: NonZeroUsize,
469 live: bool,
470}
471
472type Judged = (Vec<Version>, Vec<HeldBy>, bool);
474
475fn past_held(rows: &[Row], cap: usize, floor: &Floor, below: Version) -> Option<Version> {
479 if rows.len() <= cap {
480 return None;
481 }
482 let passed = rows.get(..cap)?;
483 if !passed
484 .iter()
485 .all(|row| floor_holds(floor, row_version(row)))
486 {
487 return None;
488 }
489 let generation = passed.last()?.key.generation;
490 (generation < below.generation).then(|| Version {
491 generation: generation.saturating_add(1),
492 sequence: 0,
493 })
494}
495
496fn floor_holds(floor: &Floor, at: Version) -> bool {
498 let generation = at.generation;
499 floor
500 .oldest_in(generation)
501 .is_some_and(|oldest| order(at) >= order(oldest))
502}
503
504fn decided_rows(
509 reader: &ReadOnlyDatabase,
510 service: &BranchRefRecord,
511 branch: &str,
512 decision: &Decision,
513 below: Below,
514) -> Result<Vec<Version>, Failure> {
515 let read = NonZeroUsize::new(decision.count.saturating_add(1)).unwrap_or(NonZeroUsize::MAX);
516 let rows = history::oldest_rows(
517 reader,
518 service,
519 branch,
520 (decision.first, below.version),
521 read,
522 )?;
523 let taken: Vec<Version> = rows
524 .iter()
525 .map(row_version)
526 .filter(|at| order(*at) <= order(decision.last))
527 .collect();
528 if taken.len() != decision.count {
529 return Err(Failure::new(
530 "release_decision_mismatch",
531 format!(
532 "the release decided for {branch} names {} rows from {}.{} to {}.{}, and {} \
533 are there; the decision is left",
534 decision.count,
535 decision.first.generation,
536 decision.first.sequence,
537 decision.last.generation,
538 decision.last.sequence,
539 taken.len()
540 ),
541 ));
542 }
543 Ok(taken)
544}
545
546fn held_at(
548 branch: &str,
549 rows: &[Row],
550 index: usize,
551 floor: &Floor,
552 below: Below,
553 rules: &Rules,
554) -> Vec<HeldBy> {
555 let Some(row) = rows.get(index) else {
556 return Vec::new();
557 };
558 let at = row_version(row);
559 if let Some(oldest) = floor.oldest_in(at.generation)
560 && order(at) >= order(oldest)
561 {
562 return floor
563 .held_by
564 .iter()
565 .filter(|held| held.version == oldest)
566 .cloned()
567 .collect();
568 }
569 let Some(keep) = rules.keep else {
570 return Vec::new();
571 };
572 let kept = |source| {
573 vec![HeldBy {
574 source,
575 name: branch.to_owned(),
576 version: at,
577 }]
578 };
579 let Some(versions) = keep.versions else {
580 return kept(Source::KeepVersions);
581 };
582 let Some(days) = keep.days else {
583 return kept(Source::KeepDays);
584 };
585 let head = below.version;
586 let newer = newer_than(rows, index, head);
587 if newer < versions.get() {
588 return kept(Source::KeepVersions);
589 }
590 let superseded = match rows.get(index + 1) {
593 Some(next) => Some(next.timestamp),
594 None if order(at) < order(head) && rows.len() <= index + 1 => None,
595 None => Some(below.written),
596 };
597 let old_enough = superseded.is_some_and(|superseded| {
598 rules.now.saturating_sub(superseded) >= days.saturating_mul(NANOS_A_DAY)
599 });
600 if !old_enough {
601 return kept(Source::KeepDays);
602 }
603 Vec::new()
604}
605
606fn due_at(rows: &[Row], held_by: &[HeldBy], rules: &Rules) -> Option<Timestamp> {
610 let days = rules.keep?.days?;
611 let held = held_by
612 .iter()
613 .find(|held| held.source == Source::KeepDays)?;
614 let index = rows
615 .iter()
616 .position(|row| row_version(row) == held.version)?;
617 let superseded = rows.get(index + 1)?.timestamp;
618 Some(superseded.saturating_add(days.saturating_mul(NANOS_A_DAY)))
619}
620
621fn newer_than(rows: &[Row], index: usize, head: Version) -> u64 {
637 let Some(row) = rows.get(index) else {
638 return 0;
639 };
640 let at = row_version(row);
641 if at.generation == head.generation {
642 return head.sequence.saturating_sub(at.sequence);
643 }
644 let older_after = rows
645 .get(index + 1..)
646 .unwrap_or_default()
647 .iter()
648 .filter(|later| later.key.generation < head.generation)
649 .count();
650 head.sequence
652 .saturating_add(1)
653 .saturating_add(u64::try_from(older_after).unwrap_or(0))
654}
655
656#[derive(Debug, Clone, PartialEq, Eq)]
662pub struct UndatedGeneration {
663 pub branch: String,
664 pub generation: u64,
666 pub at: Timestamp,
668}
669
670const UNDATED_PAGE: usize = 256;
672
673#[cfg(test)]
676#[derive(Debug, Clone, Copy, PartialEq, Eq)]
677pub enum Crash {
678 AfterDecision,
680 DuringFiles,
682 AfterFiles,
684 AfterCommit,
686 BeforeTrim,
689}
690
691#[cfg(test)]
692thread_local! {
693 static CRASH: std::cell::Cell<Option<Crash>> = const { std::cell::Cell::new(None) };
694}
695
696#[cfg(test)]
698pub fn crash_at(point: Crash) {
699 CRASH.with(|crash| crash.set(Some(point)));
700}
701
702#[cfg(test)]
703fn crashed(point: Crash) -> Result<(), Failure> {
704 if CRASH.with(|crash| crash.get() == Some(point)) {
705 CRASH.with(|crash| crash.set(None));
706 return Err(Failure::new("release_crashed", format!("{point:?}")));
707 }
708 Ok(())
709}
710
711impl Storage {
712 pub fn release(
734 &mut self,
735 branch: &str,
736 limit: Option<NonZeroUsize>,
737 rules: &Rules,
738 discards: &dyn PendingDiscards,
739 ) -> Result<Answer, Failure> {
740 let answer = self.release_once(branch, limit, rules, discards);
741 self.kept_rows.released(branch);
746 answer
747 }
748
749 #[must_use]
752 pub fn branch_names(&self) -> Vec<String> {
753 self.refs
754 .list_prefix(BRANCH_PREFIX)
755 .filter_map(|record| record.name.strip_prefix(BRANCH_PREFIX))
756 .map(str::to_owned)
757 .collect()
758 }
759
760 pub fn decided_branches(&self) -> Result<Vec<String>, Failure> {
767 let service = self
768 .refs
769 .get(SERVICE)
770 .ok_or_else(|| Failure::new("reference_missing", "missing service namespace"))?;
771 let decided = history::decided(&self.reader, service)?;
772 Ok(self
773 .refs
774 .list_prefix(BRANCH_PREFIX)
775 .filter_map(|record| record.name.strip_prefix(BRANCH_PREFIX))
776 .filter(|name| decided.contains(&Hash::of(name.as_bytes())))
777 .map(str::to_owned)
778 .collect())
779 }
780
781 #[must_use]
783 pub fn advanced_since_release(&self, branch: &str) -> usize {
784 self.kept_rows.advanced(branch)
785 }
786
787 #[must_use]
790 pub fn sync_counts(&self) -> SyncCounts {
791 self.writer.sync_counts()
792 }
793
794 fn release_once(
795 &mut self,
796 branch: &str,
797 limit: Option<NonZeroUsize>,
798 rules: &Rules,
799 discards: &dyn PendingDiscards,
800 ) -> Result<Answer, Failure> {
801 let limit = match limit {
802 Some(limit) if limit > rules.max_rows => {
803 return Err(Failure::new(
804 "release_limit_exceeded",
805 format!(
806 "limit {limit} is above retention.release_max_rows {}",
807 rules.max_rows
808 ),
809 ));
810 }
811 Some(limit) => limit,
812 None => rules.max_rows,
813 };
814 let service = self
815 .refs
816 .get(SERVICE)
817 .cloned()
818 .ok_or_else(|| Failure::new("reference_missing", "missing service namespace"))?;
819 let live = self.refs.get(&branch_name(branch)).is_some();
820 let pending = history::read_decision(&self.reader, &service, branch)?;
821 let floor = match (&pending, live) {
825 (Some(_decision), _) => None,
826 (None, true) => Some(self.relied_on(branch, discards)?),
827 (None, false) => Some(self.relied_on_by_others(branch, false, discards)?),
828 };
829 let (below, mark) =
830 self.below(&service, branch, pending.as_ref(), floor.as_ref(), limit)?;
831 let (taken, held_by, more, branches_read, due) = if let Some(decision) = &pending {
832 let taken = decided_rows(&self.reader, &service, branch, decision, below)?;
833 (taken, Vec::new(), false, 0, None)
834 } else {
835 let floor = floor.unwrap_or_default();
836 let from = if live {
837 LOWEST
838 } else {
839 past_mark(mark, below.version)
840 };
841 let read = ReadFrom {
842 from,
843 below,
844 limit,
845 live,
846 };
847 let (rows, (taken, held_by, more)) =
848 self.judge_from(&service, branch, read, &floor, rules)?;
849 let mut due = due_at(&rows, &held_by, rules);
850 if live {
851 let trim_due = self.trim_past_days(branch, rules)?;
852 due = due.into_iter().chain(trim_due).min();
853 }
854 (taken, held_by, more, floor.branches_read, due)
855 };
856 let mut answer = Answer {
857 branch: branch.to_owned(),
858 released: None,
859 marker: mark,
860 held_by,
861 more,
862 branches_read,
863 pass_asked: false,
864 pass_refused: None,
865 due_at: due,
866 };
867 let span = match (taken.first(), taken.last()) {
868 (Some(first), Some(last)) => Some(Span {
869 first: *first,
870 last: *last,
871 }),
872 _ => None,
873 };
874 if pending.is_none() {
875 let Some(span) = span else {
876 return Ok(answer);
877 };
878 let decision = Decision {
879 first: span.first,
880 last: span.last,
881 count: taken.len(),
882 at: rules.now,
883 };
884 let bytes = serde_json::to_vec(&decision).map_err(Failure::storage)?;
885 self.commit_service(&[(history::decision_key(branch), Some(bytes))])?;
886 #[cfg(test)]
887 crashed(Crash::AfterDecision)?;
888 }
889 let ended = dead::ended(&self.reader, &service, branch, &taken)?;
890 self.carry_out(branch, &taken, span, rules.now, &ended, &mut answer)?;
891 Ok(answer)
892 }
893
894 fn judge_from(
906 &self,
907 service: &BranchRefRecord,
908 branch: &str,
909 mut read: ReadFrom,
910 floor: &Floor,
911 rules: &Rules,
912 ) -> Result<(Vec<Row>, Judged), Failure> {
913 let cap = read.limit.get();
914 let ask = NonZeroUsize::new(cap.saturating_add(1)).unwrap_or(NonZeroUsize::MAX);
915 let mut skips = 0;
916 loop {
917 let span = (read.from, read.below.version);
918 let mut rows = history::oldest_rows(&self.reader, service, branch, span, ask)?;
919 if rows.len() <= cap {
920 let newest = rows.last();
921 rows.extend(self.after_newest(service, branch, newest, read.below, read.live)?);
922 }
923 let judged = judge(branch, &rows, cap, floor, read.below, rules);
924 match past_held(&rows, cap, floor, read.below.version) {
925 Some(from) if skips < cap => {
926 read.from = from;
927 skips += 1;
928 }
929 _ => return Ok((rows, judged)),
930 }
931 }
932 }
933
934 fn after_newest(
947 &self,
948 service: &BranchRefRecord,
949 branch: &str,
950 newest: Option<&Row>,
951 below: Below,
952 live: bool,
953 ) -> Result<Option<Row>, Failure> {
954 let written = match newest {
955 Some(newest) if live && newest.key.generation != below.version.generation => {
956 let generation = newest.key.generation;
957 match dead::marker(&self.reader, service, branch, generation)? {
958 Some(dead) => dead.discarded_at,
959 None => return Ok(None),
960 }
961 }
962 Some(newest) if live => self
963 .refs
964 .kept(&branch_name(branch))
965 .iter()
966 .find(|kept| kept.seq > newest.key.sequence)
967 .map_or(below.written, |kept| kept.timestamp),
968 _ => below.written,
969 };
970 Ok(Some(dead::discard_row(branch, below.version, written)))
971 }
972
973 fn below(
981 &self,
982 service: &BranchRefRecord,
983 branch: &str,
984 pending: Option<&Decision>,
985 floor: Option<&Floor>,
986 limit: NonZeroUsize,
987 ) -> Result<(Below, Option<Released>), Failure> {
988 if let Some(record) = self.refs.get(&branch_name(branch)) {
989 let below = Below {
990 version: version(record),
991 written: record.timestamp,
992 };
993 let generation = below.version.generation;
994 let mark = history::read_released(&self.reader, service, branch, generation)?;
995 return Ok((below, mark));
996 }
997 let markers = dead::of_name(&self.reader, service, branch, limit)?;
998 if let (None, Some(floor)) = (pending, floor)
999 && let Some(unheld) = self.first_unheld(service, branch, &markers, floor)?
1000 {
1001 return Ok(unheld);
1002 }
1003 let dead = pending
1004 .and_then(|decision| {
1005 markers
1006 .iter()
1007 .find(|dead| dead.last.generation == decision.last.generation)
1008 })
1009 .or_else(|| markers.first())
1010 .ok_or_else(|| Failure::new("branch_missing", branch))?;
1011 let generation = dead.last.generation;
1012 let mark = history::read_released(&self.reader, service, branch, generation)?;
1013 Ok((ending(dead), mark))
1014 }
1015
1016 fn first_unheld(
1022 &self,
1023 service: &BranchRefRecord,
1024 branch: &str,
1025 markers: &[dead::Dead],
1026 floor: &Floor,
1027 ) -> Result<Option<(Below, Option<Released>)>, Failure> {
1028 for dead in markers {
1029 let below = ending(dead);
1030 let generation = dead.last.generation;
1031 let mark = history::read_released(&self.reader, service, branch, generation)?;
1032 let from = past_mark(mark, below.version);
1033 if order(from) >= order(below.version) {
1034 continue;
1035 }
1036 let held = floor
1037 .oldest_in(generation)
1038 .is_some_and(|relied| order(relied) <= order(from));
1039 if !held {
1040 return Ok(Some((below, mark)));
1041 }
1042 }
1043 Ok(None)
1044 }
1045
1046 pub fn date_undated_generation(
1058 &mut self,
1059 request: &UndatedGeneration,
1060 ) -> Result<Version, Failure> {
1061 let branch = request.branch.as_str();
1062 if self.refs.get(&branch_name(branch)).is_some() {
1063 return Err(Failure::new(
1064 "branch_live",
1065 format!("{branch} has a record"),
1066 ));
1067 }
1068 let service = self
1069 .refs
1070 .get(SERVICE)
1071 .cloned()
1072 .ok_or_else(|| Failure::new("reference_missing", "missing service namespace"))?;
1073 let generation = request.generation;
1074 if dead::marker(&self.reader, &service, branch, generation)?.is_some() {
1075 return Err(Failure::new(
1076 "generation_marked",
1077 format!("{branch} generation {generation} has its marker"),
1078 ));
1079 }
1080 let newest = newest_row(&self.reader, &service, branch, generation)?;
1081 let Some(last) = newest else {
1082 return Err(Failure::new(
1083 "generation_empty",
1084 format!("{branch} generation {generation} has no rows"),
1085 ));
1086 };
1087 let dead = dead::Dead {
1088 branch: branch.to_owned(),
1089 last,
1090 discarded_at: request.at,
1091 };
1092 let bytes = serde_json::to_vec(&dead).map_err(Failure::storage)?;
1093 self.commit_service(&[(dead::dead_key(branch, generation), Some(bytes))])?;
1094 Ok(last)
1095 }
1096
1097 fn trim_past_days(
1110 &mut self,
1111 branch: &str,
1112 rules: &Rules,
1113 ) -> Result<Option<Timestamp>, Failure> {
1114 let Some((versions, days)) = rules.keep.and_then(|keep| keep.versions.zip(keep.days))
1115 else {
1116 return Ok(None);
1117 };
1118 let name = branch_name(branch);
1119 let Some(record) = self.refs.get(&name).cloned() else {
1120 return Ok(None);
1121 };
1122 let kept = self.refs.kept(&name);
1123 if let Some(keep_from) = past_days(&record, kept, versions, days, rules.now) {
1124 self.rows_before_trim(branch, &record, keep_from)?;
1125 #[cfg(test)]
1126 crashed(Crash::BeforeTrim)?;
1127 self.refs
1128 .trim_kept(&name, record.created, record.seq, keep_from)
1129 .map_err(|error| Failure::new("release_kept_untrimmed", error.to_string()))?;
1130 self.publish_record(&name)?;
1132 }
1133 Ok(kept_due(&record, self.refs.kept(&name), versions, days))
1134 }
1135
1136 fn carry_out(
1141 &mut self,
1142 branch: &str,
1143 taken: &[Version],
1144 span: Option<Span>,
1145 now: Timestamp,
1146 ended: &[Vec<u8>],
1147 answer: &mut Answer,
1148 ) -> Result<(), Failure> {
1149 let files: Vec<String> = taken
1150 .iter()
1151 .map(|at| history::file_name(branch, *at))
1152 .collect();
1153 #[cfg(test)]
1154 if CRASH.with(|crash| crash.get() == Some(Crash::DuringFiles)) {
1155 let half = files.len() / 2;
1156 self.remove_files(files.get(..half).unwrap_or_default())?;
1157 crashed(Crash::DuringFiles)?;
1158 }
1159 self.remove_files(&files)?;
1160 #[cfg(test)]
1161 crashed(Crash::AfterFiles)?;
1162 let mut writes: Vec<(Vec<u8>, Option<Vec<u8>>)> = taken
1163 .iter()
1164 .map(|at| (history::row_key(branch, *at), None))
1165 .collect();
1166 writes.push((history::decision_key(branch), None));
1167 writes.extend(ended.iter().map(|key| (key.clone(), None)));
1169 for last in last_of_each_generation(taken) {
1172 let released = Released {
1173 version: last,
1174 at: now,
1175 };
1176 let bytes = serde_json::to_vec(&released).map_err(Failure::storage)?;
1177 writes.push((
1178 history::released_key_at(branch, last.generation),
1179 Some(bytes),
1180 ));
1181 }
1182 if let Some(span) = span {
1183 answer.marker = Some(Released {
1184 version: span.last,
1185 at: now,
1186 });
1187 }
1188 self.commit_service(&writes)?;
1189 answer.released = span;
1190 #[cfg(test)]
1191 crashed(Crash::AfterCommit)?;
1192 match self.writer.request_reclaim() {
1195 Ok(()) => answer.pass_asked = true,
1196 Err(error) => answer.pass_refused = Some(error.to_string()),
1197 }
1198 Ok(())
1199 }
1200
1201 fn remove_files(&mut self, names: &[String]) -> Result<(), Failure> {
1204 let removed = self
1205 .refs
1206 .remove_set(names.iter().map(String::as_str))
1207 .map_err(|error| Failure::new("release_files_unremoved", error.to_string()))?;
1208 for record in removed.removed {
1209 self.visible.remove(&record.name)?;
1210 }
1211 Ok(())
1212 }
1213
1214 fn commit_service(&mut self, writes: &[(Vec<u8>, Option<Vec<u8>>)]) -> Result<(), Failure> {
1218 let committed = self.try_commit_service(writes);
1219 if committed.is_err() {
1220 self.kept_rows.mark_unsure();
1221 }
1222 committed
1223 }
1224
1225 fn try_commit_service(&mut self, writes: &[(Vec<u8>, Option<Vec<u8>>)]) -> Result<(), Failure> {
1226 let at = self
1227 .refs
1228 .get(SERVICE)
1229 .map(version)
1230 .ok_or_else(|| Failure::new("reference_missing", "missing service namespace"))?;
1231 let service =
1232 open_branch(SERVICE, &self.refs, &self.registry).map_err(Failure::branch_commit)?;
1233 for (key, value) in writes {
1234 let shard = self.writer.shard_for(key);
1235 value
1236 .as_ref()
1237 .map_or_else(
1238 || service.delete(shard, key),
1239 |value| service.put(shard, key, value),
1240 )
1241 .map_err(Failure::branch)?;
1242 }
1243 self.writer
1244 .commit(
1245 &service,
1246 &self.registry,
1247 CommitRequest {
1248 durability: CommitDurability::Durable {
1249 refs: &mut self.refs,
1250 },
1251 extra_parents: &[],
1252 timestamp: haematite::branch::current_timestamp(),
1253 },
1254 )
1255 .map_err(|error| {
1256 let installed = self.refs.get(SERVICE).is_some_and(|now| version(now) != at);
1257 Failure::new(
1258 if installed {
1259 "durability_unknown"
1260 } else {
1261 "write_refused"
1262 },
1263 error.to_string(),
1264 )
1265 })?;
1266 self.publish_record(SERVICE)
1267 }
1268}
1269
1270const fn past_mark(mark: Option<Released>, below: Version) -> Version {
1275 match mark {
1276 Some(mark) if mark.version.generation > below.generation => below,
1277 Some(mark) => after(mark),
1278 None => Version {
1279 generation: below.generation,
1280 sequence: 0,
1281 },
1282 }
1283}
1284
1285fn newest_row(
1288 reader: &ReadOnlyDatabase,
1289 service: &BranchRefRecord,
1290 branch: &str,
1291 generation: u64,
1292) -> Result<Option<Version>, Failure> {
1293 let page = NonZeroUsize::new(UNDATED_PAGE).unwrap_or(NonZeroUsize::MIN);
1294 let end = Version {
1295 generation,
1296 sequence: u64::MAX,
1297 };
1298 let mut from = Version {
1299 generation,
1300 sequence: 0,
1301 };
1302 let mut newest = None;
1303 loop {
1304 let rows = history::oldest_rows(reader, service, branch, (from, end), page)?;
1305 let Some(last) = rows.last().map(row_version) else {
1306 return Ok(newest);
1307 };
1308 newest = Some(last);
1309 if rows.len() < page.get() {
1310 return Ok(newest);
1311 }
1312 from = Version {
1313 generation,
1314 sequence: last.sequence.saturating_add(1),
1315 };
1316 }
1317}
1318
1319fn past_days(
1324 record: &BranchRefRecord,
1325 kept: &[KeptHead],
1326 versions: NonZeroU64,
1327 days: u64,
1328 now: Timestamp,
1329) -> Option<u64> {
1330 let age = days.saturating_mul(NANOS_A_DAY);
1331 let superseded = kept
1332 .iter()
1333 .skip(1)
1334 .map(|next| next.timestamp)
1335 .chain(std::iter::once(record.timestamp));
1336 let leaving = kept
1337 .iter()
1338 .zip(superseded)
1339 .take_while(|(entry, superseded)| {
1340 record.seq.saturating_sub(entry.seq) >= versions.get()
1341 && now.saturating_sub(*superseded) >= age
1342 })
1343 .count();
1344 let keep_from = kept.get(leaving).map_or(record.seq, |entry| entry.seq);
1345 (leaving > 0).then_some(keep_from)
1346}
1347
1348fn kept_due(
1351 record: &BranchRefRecord,
1352 kept: &[KeptHead],
1353 versions: NonZeroU64,
1354 days: u64,
1355) -> Option<Timestamp> {
1356 let index = kept
1357 .iter()
1358 .position(|entry| record.seq.saturating_sub(entry.seq) >= versions.get())?;
1359 let superseded = kept
1360 .get(index + 1)
1361 .map_or(record.timestamp, |next| next.timestamp);
1362 Some(superseded.saturating_add(days.saturating_mul(NANOS_A_DAY)))
1363}
1364
1365const fn ending(dead: &dead::Dead) -> Below {
1368 Below {
1369 version: Version {
1370 generation: dead.last.generation,
1371 sequence: dead.last.sequence.saturating_add(1),
1372 },
1373 written: dead.discarded_at,
1374 }
1375}
1376
1377fn last_of_each_generation(taken: &[Version]) -> Vec<Version> {
1379 let mut last: Vec<Version> = Vec::new();
1380 for at in taken {
1381 match last.last_mut() {
1382 Some(previous) if previous.generation == at.generation => *previous = *at,
1383 _ => last.push(*at),
1384 }
1385 }
1386 last
1387}
1388
1389const fn after(released: Released) -> Version {
1391 Version {
1392 generation: released.version.generation,
1393 sequence: released.version.sequence.saturating_add(1),
1394 }
1395}
1396
1397#[cfg(test)]
1398#[path = "release_tests.rs"]
1399mod tests;