1use super::{DurabilityError, DurableStore, ScanCursor, ScanPage, StoredEntry};
3use std::collections::BTreeMap;
4use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
5use std::sync::{Arc, Mutex, MutexGuard};
6
7#[derive(Debug)]
9pub struct HeadCheckpointStop {
10 inner: Arc<dyn DurableStore>,
11 touched: Mutex<BTreeMap<String, u64>>,
12 stopping: AtomicBool,
13 generation: AtomicU64,
14}
15
16impl HeadCheckpointStop {
17 #[must_use]
19 pub fn new(inner: Arc<dyn DurableStore>) -> Self {
20 Self {
21 inner,
22 touched: Mutex::default(),
23 stopping: AtomicBool::new(false),
24 generation: AtomicU64::new(0),
25 }
26 }
27
28 pub async fn checkpoint_heads(&self) -> Result<(), DurabilityError> {
33 self.stopping.store(true, Ordering::Release);
34 self.advance_touched_heads().await
35 }
36
37 fn touched(&self) -> Result<MutexGuard<'_, BTreeMap<String, u64>>, DurabilityError> {
38 self.touched.lock().map_err(|_| {
39 DurabilityError::ConfigError("clean-stop head tracking lock poisoned".to_owned())
40 })
41 }
42
43 fn note_append(&self, stream: &str) -> Result<(), DurabilityError> {
44 let generation = self
47 .generation
48 .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |previous| {
49 previous.checked_add(1)
50 })
51 .map_err(|_| {
52 DurabilityError::ConfigError(
53 "clean-stop head tracking generation exhausted".to_owned(),
54 )
55 })?;
56 self.touched()?.insert(stream.to_owned(), generation);
57 Ok(())
58 }
59
60 async fn advance_touched_heads(&self) -> Result<(), DurabilityError> {
61 let touched = self.touched()?.clone();
62 for (stream, generation) in touched {
63 if let Some(next) = self.inner.read_stream_next_seq(&stream).await? {
64 let key = super::head_checkpoint_key(&stream);
65 let recorded = self.inner.read_value(&key).await?.unwrap_or(0);
66 if recorded > next {
67 return Err(DurabilityError::ConfigError(format!(
68 "clean-stop head {recorded} exceeds stream {stream} head {next}"
69 )));
70 }
71 if recorded < next {
72 self.inner.cas(&key, recorded, next).await?;
73 }
74 }
75 let mut current = self.touched()?;
76 if current.get(&stream) == Some(&generation) {
77 current.remove(&stream);
78 }
79 }
80 Ok(())
81 }
82}
83
84#[async_trait::async_trait]
85impl DurableStore for HeadCheckpointStop {
86 fn reclaim_spine(&self) -> Option<Arc<haematite::EventStore>> {
87 self.inner.reclaim_spine()
88 }
89 fn max_request_bytes(&self) -> Option<usize> {
90 self.inner.max_request_bytes()
91 }
92 fn store_counts(&self) -> Option<super::StoreCountsHandle> {
93 self.inner.store_counts()
94 }
95 async fn truncate_before(
96 &self,
97 stream_key: &str,
98 before_seq: u64,
99 ) -> Result<u64, DurabilityError> {
100 self.inner.truncate_before(stream_key, before_seq).await
101 }
102 async fn scan_routed(
103 &self,
104 route_key: &str,
105 from: &[u8],
106 to: &[u8],
107 ) -> Result<Vec<(Vec<u8>, Vec<u8>)>, DurabilityError> {
108 self.inner.scan_routed(route_key, from, to).await
109 }
110 async fn remove_routed_range(
111 &self,
112 route_key: &str,
113 from: &[u8],
114 to: &[u8],
115 ) -> Result<u64, DurabilityError> {
116 self.inner.remove_routed_range(route_key, from, to).await
117 }
118 async fn apply_routed_batch(
119 &self,
120 route_key: &str,
121 items: Vec<crate::durability::RoutedBatchItem>,
122 ) -> Result<(), crate::durability::DurabilityError> {
123 self.inner.apply_routed_batch(route_key, items).await
124 }
125
126 async fn apply_routed_entries(
127 &self,
128 route_key: &str,
129 entries: Vec<crate::durability::RoutedBatchEntry>,
130 ) -> Result<(), crate::durability::DurabilityError> {
131 self.inner.apply_routed_entries(route_key, entries).await
132 }
133
134 async fn get_routed(
135 &self,
136 route_key: &str,
137 key: &[u8],
138 ) -> Result<Option<Vec<u8>>, crate::durability::DurabilityError> {
139 self.inner.get_routed(route_key, key).await
140 }
141
142 async fn append(
143 &self,
144 stream_key: &str,
145 payload: Vec<u8>,
146 expected_seq: u64,
147 ) -> Result<u64, DurabilityError> {
148 self.note_append(stream_key)?;
149 self.inner.append(stream_key, payload, expected_seq).await
150 }
151
152 async fn append_all(
153 &self,
154 stream_key: &str,
155 payloads: Vec<Vec<u8>>,
156 expected_seq: u64,
157 ) -> Result<u64, DurabilityError> {
158 self.note_append(stream_key)?;
159 self.inner
160 .append_all(stream_key, payloads, expected_seq)
161 .await
162 }
163
164 async fn read_from(
165 &self,
166 stream_key: &str,
167 offset: u64,
168 limit: usize,
169 ) -> Result<Vec<StoredEntry>, DurabilityError> {
170 self.inner.read_from(stream_key, offset, limit).await
171 }
172
173 async fn read_at(
174 &self,
175 stream_key: &str,
176 sequence: u64,
177 ) -> Result<Option<StoredEntry>, DurabilityError> {
178 self.inner.read_at(stream_key, sequence).await
179 }
180
181 async fn cas(&self, key: &str, old_value: u64, new_value: u64) -> Result<(), DurabilityError> {
182 self.inner.cas(key, old_value, new_value).await
183 }
184
185 async fn read_stream_next_seq(&self, stream_key: &str) -> Result<Option<u64>, DurabilityError> {
186 self.inner.read_stream_next_seq(stream_key).await
187 }
188
189 async fn read_value(&self, key: &str) -> Result<Option<u64>, DurabilityError> {
190 self.inner.read_value(key).await
191 }
192
193 async fn scan_page(
194 &self,
195 prefix: &str,
196 cursor: Option<ScanCursor>,
197 limit: usize,
198 ) -> Result<ScanPage, DurabilityError> {
199 self.inner.scan_page(prefix, cursor, limit).await
200 }
201
202 async fn flush(&self) -> Result<(), DurabilityError> {
203 if self.stopping.load(Ordering::Acquire) {
206 self.advance_touched_heads().await?;
207 }
208 self.inner.flush().await
209 }
210}