1use crate::{ByteRange, CaptureConsistency, FileChunk, FileReader, FileReference, Result, StorageFile, StorageManager};
19use std::{
20 future::Future,
21 pin::Pin,
22 sync::{
23 Arc, Mutex,
24 atomic::{AtomicU64, Ordering},
25 },
26};
27
28type Pending = Pin<Box<dyn Future<Output = (State, Result<Step>)> + Send>>;
41
42pub struct OpenedCapture {
46 state: Option<State>,
47 pending: Mutex<Option<Pending>>,
48 acknowledged: Option<Arc<AtomicU64>>,
49 initial_offset: u64,
50 request: Option<Request>,
51}
52
53struct State {
54 session: crate::StorageManager<crate::ErasedCatalog>,
55 file: std::fs::File,
56 range: ByteRange,
57 reader: Option<FileReader>,
58 target: StorageFile,
59 consistency: CaptureConsistency,
60 chunk: Option<FileChunk>,
61 offset: u64,
62 acknowledged: Option<Arc<AtomicU64>>,
63 prefix: Option<crate::ProductionFollower>,
64 remaining_prefix: u64,
65 matched: usize,
66 conflict: bool,
67 request: Request,
68 action: Action,
69 in_round: bool,
70 observed: Option<(u64, std::time::SystemTime)>,
71 sealed: bool,
72 finished: Option<FileReference>,
73 _frozen: Option<crate::FrozenFile>,
74}
75
76#[derive(Clone, Copy, PartialEq, Eq)]
79enum Request {
80 Finish,
81 Synchronize(u64),
82 FinishAt(u64),
83 Interrupt,
84}
85
86#[derive(Clone, Copy)]
87enum Action {
88 Recover,
89 Open,
90 Prefix,
91 Read,
92 Match,
93 Check,
94 Write,
95 Seal,
96}
97
98enum Step {
99 Continue,
100 Synchronized(bool),
101 Finished,
102}
103
104impl<C> StorageManager<C> {
109 pub fn capture_frozen_file(&self, source: crate::FrozenFile, target: StorageFile) -> Result<OpenedCapture> {
110 let file = source.opened_file()?;
111 let range = ByteRange { offset: 0, limit: Some(source.length()) };
112 let mut capture = self.capture_opened_file(file, target, range, CaptureConsistency::Stable);
113 let state = capture.state.as_mut().expect("new capture retains source state");
114 if Arc::ptr_eq(&self.inner, &source.session.inner) {
115 state.session = source.session.clone();
116 }
117 state._frozen = Some(source);
118 Ok(capture)
119 }
120
121 pub fn capture_opened_file(
122 &self, file: std::fs::File, target: StorageFile, range: ByteRange, consistency: CaptureConsistency,
123 ) -> OpenedCapture {
124 let offset = target.length();
125 OpenedCapture {
126 state: Some(State {
127 session: self.erased(),
128 file,
129 range,
130 reader: None,
131 target,
132 consistency,
133 chunk: None,
134 offset,
135 acknowledged: None,
136 prefix: None,
137 remaining_prefix: offset,
138 matched: 0,
139 conflict: false,
140 request: Request::Finish,
141 action: Action::Recover,
142 in_round: false,
143 observed: None,
144 sealed: false,
145 finished: None,
146 _frozen: None,
147 }),
148 pending: Mutex::new(None),
149 acknowledged: None,
150 initial_offset: offset,
151 request: None,
152 }
153 }
154}
155
156impl OpenedCapture {
157 pub fn acknowledged(&self) -> u64 {
158 self.acknowledged.as_ref().map_or(self.initial_offset, |value| value.load(Ordering::Acquire))
159 }
160
161 pub async fn finish(&mut self) -> Result<FileReference> {
162 match self.run(Request::Finish).await? {
163 Step::Finished => Ok(self.finished()),
164 _ => unreachable!("terminal capture returns its reference"),
165 }
166 }
167
168 pub async fn synchronize(&mut self, visible_length: u64) -> Result<bool> {
171 match self.run(Request::Synchronize(visible_length)).await? {
172 Step::Synchronized(stable) => Ok(stable),
173 _ => unreachable!("nonterminal capture returns its observation"),
174 }
175 }
176
177 pub async fn finish_at(&mut self, visible_length: u64) -> Result<FileReference> {
179 match self.run(Request::FinishAt(visible_length)).await? {
180 Step::Finished => Ok(self.finished()),
181 _ => unreachable!("terminal capture returns its reference"),
182 }
183 }
184
185 pub async fn interrupt(&mut self) -> Result<FileReference> {
187 match self.run(Request::Interrupt).await? {
188 Step::Finished => Ok(self.finished()),
189 _ => unreachable!("interrupted capture returns its reference"),
190 }
191 }
192
193 fn finished(&mut self) -> FileReference {
194 self.state.as_mut().expect("capture state retained").finished.take().expect("finished capture retains its reference")
195 }
196
197 async fn run(&mut self, request: Request) -> Result<Step> {
198 let changed = self.request != Some(request);
199 if changed && self.pending.get_mut().expect("capture pending owner is healthy").is_some() {
200 let settled = self.step().await;
203 if request != Request::Interrupt {
204 settled?;
205 }
206 }
207 if self.state.as_ref().is_some_and(|state| changed || !state.in_round) {
208 self.state.as_mut().expect("capture state retained").reset(request)?;
209 self.request = Some(request);
210 }
211 loop {
212 match self.step().await? {
213 Step::Continue => {}
214 outcome => return Ok(outcome),
215 }
216 }
217 }
218
219 async fn step(&mut self) -> Result<Step> {
220 let pending = self.pending.get_mut().expect("capture pending owner is healthy");
221 if pending.is_none() {
222 if self.acknowledged.is_none() {
223 let acknowledged = Arc::new(AtomicU64::new(self.initial_offset));
224 self.state.as_mut().expect("capture owns its state").acknowledged = Some(acknowledged.clone());
225 self.acknowledged = Some(acknowledged);
226 }
227 let state = self.state.take().expect("capture owns its state");
228 *pending = Some(Box::pin(work(state)));
229 }
230 let (state, result) = pending.as_mut().expect("accepted capture retained").await;
231 *pending = None;
232 self.state = Some(state);
233 result
234 }
235}
236
237impl State {
238 fn reset(&mut self, request: Request) -> Result<()> {
239 self.finished = None;
240 if self.sealed && matches!(request, Request::Synchronize(_)) {
241 return Err(crate::Error::InvalidInput {
242 field: "opened capture",
243 reason: "producer is already sealed",
244 });
245 }
246 if self.sealed && matches!(request, Request::FinishAt(length) if length != self.offset) {
247 return Err(conflict());
248 }
249 self.request = request;
250 self.in_round = true;
251 self.action = if self.sealed { Action::Seal } else { Action::Recover };
252 self.reader = None;
253 self.prefix = None;
254 self.chunk = None;
255 self.matched = 0;
256 self.remaining_prefix = 0;
257 Ok(())
258 }
259
260 async fn advance(&mut self) -> Result<Step> {
261 if self.conflict && self.request != Request::Interrupt {
262 return Err(conflict());
263 }
264 match self.action {
265 Action::Recover => {
266 self.target.retry_append().await?;
267 self.offset = self.target.length();
268 self.acknowledged.as_ref().expect("acknowledgement retained").store(self.offset, Ordering::Release);
269 self.action = if self.request == Request::Interrupt { Action::Seal } else { Action::Open };
270 }
271 Action::Open => {
272 let mut range = self.range;
273 if let Request::Synchronize(length) | Request::FinishAt(length) = self.request {
274 if length < self.offset {
275 self.conflict = true;
276 return Err(conflict());
277 }
278 if range.limit.is_some_and(|limit| length > limit) {
279 return Err(crate::Error::InvalidInput {
280 field: "opened capture",
281 reason: "visible length exceeds selected range",
282 });
283 }
284 range.limit = Some(length);
285 }
286 let file = self.file.try_clone().map_err(crate::local::io_error)?;
287 let mut reader = self.session.read_opened_file(file, range).await?;
288 self.remaining_prefix = self.offset;
289 if let Request::Synchronize(length) | Request::FinishAt(length) = self.request {
290 let observation = reader.observation().expect("opened capture reads its original handle");
291 let retained_end = self.range.offset.checked_add(self.offset).ok_or_else(conflict)?;
292 if observation.0 < retained_end {
293 self.conflict = true;
294 return Err(conflict());
295 }
296 let visible_end = self.range.offset.checked_add(length).ok_or_else(conflict)?;
297 if observation.0 < visible_end {
298 return Err(crate::Error::InvalidInput {
299 field: "opened capture",
300 reason: "visible range exceeds opened source",
301 });
302 }
303 if matches!(self.request, Request::Synchronize(_)) && self.observed == Some(observation) {
304 reader.start_opened(retained_end)?;
305 self.remaining_prefix = 0;
306 }
307 }
308 self.reader = Some(reader);
309 self.action = if self.remaining_prefix == 0 { Action::Read } else { Action::Prefix };
310 }
311 Action::Prefix => {
312 let token = self.target.session().ok_or(crate::Error::InvalidInput {
313 field: "opened capture",
314 reason: "existing producer is not resumable",
315 })?;
316 let staging = crate::StagedFile {
317 history: token.history.clone(),
318 id: token.id.clone(),
319 };
320 self.prefix = Some(self.session.follow_production(&staging, 0).await?);
321 self.action = Action::Read;
322 }
323 Action::Read => {
324 let Some(batch) = self.reader.as_mut().expect("opened source retained").next_raw(false).await? else {
325 if self.remaining_prefix != 0 {
326 self.conflict = true;
327 return Err(conflict());
328 }
329 self.reader.as_ref().expect("opened source retained").completion()?;
330 self.action = Action::Check;
331 return Ok(Step::Continue);
332 };
333 self.chunk = Some(FileChunk::new(batch.bytes));
334 self.action = if self.remaining_prefix == 0 { Action::Check } else { Action::Match };
335 }
336 Action::Match => {
337 let chunk = self.chunk.as_ref().expect("exact capture bytes retained");
338 let count = (chunk.len() - self.matched).min(usize::try_from(self.remaining_prefix).unwrap_or(usize::MAX));
339 let batch = self
340 .prefix
341 .as_mut()
342 .expect("retained prefix reader")
343 .next_batch(std::num::NonZeroUsize::new(count).unwrap())
344 .await?
345 .ok_or_else(conflict)?;
346 let count = batch.bytes().len();
347 if batch.bytes() != &chunk.as_slice()[self.matched..self.matched + count] {
348 self.conflict = true;
349 return Err(conflict());
350 }
351 self.matched += count;
352 self.remaining_prefix -= count as u64;
353 if self.matched == chunk.len() {
354 self.chunk = None;
355 self.matched = 0;
356 self.action = Action::Read;
357 } else if self.remaining_prefix == 0 {
358 self.action = Action::Check;
359 }
360 if self.remaining_prefix == 0 {
361 self.prefix = None;
362 }
363 }
364 Action::Check => {
365 let reader = self.reader.as_ref().expect("opened source retained");
366 if matches!(self.request, Request::Synchronize(_) | Request::FinishAt(_)) {
367 if !reader.observation_unchanged().await? {
368 self.reader = None;
369 self.prefix = None;
370 self.chunk = None;
371 self.matched = 0;
372 self.in_round = false;
373 if matches!(self.request, Request::FinishAt(_)) {
374 return Err(crate::Error::InvalidInput {
375 field: "opened capture",
376 reason: "source changed during final capture",
377 });
378 }
379 return Ok(Step::Synchronized(false));
380 }
381 if self.chunk.is_none() {
382 self.observed = reader.observation();
383 if matches!(self.request, Request::Synchronize(_)) {
384 self.in_round = false;
385 self.reader = None;
386 return Ok(Step::Synchronized(true));
387 }
388 }
389 } else if self.chunk.is_none() && self.consistency == CaptureConsistency::Stable {
390 reader.check_unchanged().await?;
391 }
392 self.action = if self.chunk.is_some() { Action::Write } else { Action::Seal };
393 }
394 Action::Write => {
395 let chunk = self.chunk.as_mut().expect("exact capture bytes retained");
396 let suffix = chunk.slice(self.matched..chunk.len());
397 self.target.write_retained_at(self.offset, suffix.clone()).await?;
398 self.offset += suffix.len() as u64;
399 self.acknowledged.as_ref().expect("acknowledgement retained").store(self.offset, Ordering::Release);
400 self.chunk = None;
401 self.matched = 0;
402 self.action = Action::Read;
403 }
404 Action::Seal => {
405 let result = self.target.finish().await;
406 if result.is_err() && self.reader.is_some() && self.request != Request::Interrupt {
407 self.action = Action::Check;
410 }
411 self.finished = Some(result?);
412 self.sealed = true;
413 self.action = if self.request == Request::Finish && self.reader.is_some() { Action::Check } else { Action::Seal };
414 return Ok(Step::Finished);
415 }
416 }
417 Ok(Step::Continue)
418 }
419}
420
421async fn work(mut state: State) -> (State, Result<Step>) {
426 let result = state.advance().await;
427 (state, result)
428}
429
430fn conflict() -> crate::Error {
431 crate::Error::InvalidInput {
432 field: "opened capture",
433 reason: "source differs from retained producer prefix",
434 }
435}
436
437