1use super::allocation::{AllocationError, AllocationLedger, VisitedObjectSet};
4use super::codec::DecodedPageKind;
5use super::extent::{extent_page_decode_shape, extent_page_encoded_length};
6use super::{
7 CanonicalDecodeError, DecodeLimits, Extent, ExtentChild, ExtentKind, ExtentPage,
8 decode_extent_page, encode_extent_page,
9};
10use crate::cancellation::CancellationToken;
11use crate::performance::{OperationFailure, WorkBudget, WorkCounters, WorkError};
12use crate::storage::{
13 OBJECT_DIGEST_ENVELOPE_BYTES, ObjectId, ObjectKind, ObjectReadRetention, ObjectStoreError,
14 object_digest,
15};
16use bytes::Bytes;
17use std::mem::size_of;
18use thiserror::Error;
19
20#[derive(Clone, Debug, Eq, PartialEq)]
22pub enum ExtentMutation {
23 Replace {
25 offset: u64,
27 length: u64,
29 kind: ExtentKind,
31 extend: bool,
33 },
34 Resize {
36 logical_bytes: u64,
38 },
39}
40
41#[derive(Clone, Debug)]
42struct Patch {
43 offset: u64,
44 end: u64,
45 kind: ExtentKind,
46}
47
48struct CompiledPatchPlan {
49 patches: Vec<Patch>,
50 final_size: u64,
51 work: WorkCounters,
52 live_allocation_bytes: u64,
53}
54
55#[derive(Clone, Debug)]
56struct Summary {
57 first: u64,
58 end: u64,
59 page: ObjectId,
60}
61
62struct NodeRequest {
63 page: ObjectId,
64 original_first: u64,
65 original_end: u64,
66 output_end: u64,
67 height: u16,
68}
69
70struct InternalFrame {
71 original: ObjectId,
72 children: Vec<ExtentChild>,
73 original_first: u64,
74 original_end: u64,
75 output_end: u64,
76 height: u16,
77 next_child: usize,
78 rewritten: Vec<Summary>,
79 logical_bytes: u64,
80}
81
82enum EnteredNode {
83 Complete(Vec<Summary>),
84 Internal(InternalFrame),
85}
86
87struct OwnedExtentPage {
88 page: ExtentPage,
89 logical_bytes: u64,
90}
91
92#[derive(Clone, Debug, Eq, PartialEq)]
94pub struct ExtentMutationReceipt {
95 pub root: ObjectId,
97 pub logical_bytes: u64,
99 pub work: WorkCounters,
101}
102
103#[derive(Clone, Copy, Debug, Eq, PartialEq)]
105pub struct ExtentMutationOptions {
106 pub maximum_mutations: u32,
108 pub limits: DecodeLimits,
110 pub budget: WorkBudget,
112}
113
114pub type ExtentMutationFailure = OperationFailure<ExtentMutationError>;
116
117struct Context<'a, S> {
118 store: &'a S,
119 limits: DecodeLimits,
120 budget: WorkBudget,
121 work: WorkCounters,
122 allocations: AllocationLedger,
123 visited: VisitedObjectSet,
124 patches: Vec<Patch>,
125 maximum_seen_height: u16,
126 cancellation: &'a CancellationToken,
127}
128
129pub fn apply_extent_mutations<S: crate::ImmediateObjectStore>(
142 store: &S,
143 root: ObjectId,
144 old_logical_bytes: u64,
145 mutations: &[ExtentMutation],
146 maximum_mutations: u32,
147 limits: DecodeLimits,
148 budget: WorkBudget,
149) -> Result<ExtentMutationReceipt, ExtentMutationFailure> {
150 let cancellation = CancellationToken::new();
151 crate::async_storage::poll_immediate(apply_extent_mutations_async(
152 store,
153 root,
154 old_logical_bytes,
155 mutations,
156 ExtentMutationOptions {
157 maximum_mutations,
158 limits,
159 budget,
160 },
161 &cancellation,
162 ))
163}
164
165pub async fn apply_extent_mutations_async<S: crate::AsyncObjectStore>(
176 store: &S,
177 root: ObjectId,
178 old_logical_bytes: u64,
179 mutations: &[ExtentMutation],
180 options: ExtentMutationOptions,
181 cancellation: &CancellationToken,
182) -> Result<ExtentMutationReceipt, ExtentMutationFailure> {
183 cancellation.check().map_err(|_| {
184 OperationFailure::before_work(ExtentMutationError::Storage(ObjectStoreError::Cancelled))
185 })?;
186 let ExtentMutationOptions {
187 maximum_mutations,
188 limits,
189 budget,
190 } = options;
191 validate_request(root, mutations, maximum_mutations, limits)?;
192 let plan = compile_patch_plan(old_logical_bytes, mutations, budget)?;
193 let final_size = plan.final_size;
194 let mut allocations = AllocationLedger::default();
195 let mut work = plan.work;
196 allocations
197 .claim_bytes(plan.live_allocation_bytes, 0, &mut work, budget)
198 .map_err(|error| OperationFailure::new(error.into(), work))?;
199 let maximum_visited = usize::try_from(limits.maximum_visited_pages)
200 .map_err(|_| OperationFailure::new(ExtentMutationError::InvalidLimits, work))?;
201 let visited = VisitedObjectSet::new(maximum_visited, &mut allocations, &mut work, budget)
202 .map_err(|error| OperationFailure::new(error.into(), work))?;
203 let mut context = Context {
204 store,
205 limits,
206 budget,
207 work,
208 allocations,
209 visited,
210 patches: plan.patches,
211 maximum_seen_height: 0,
212 cancellation,
213 };
214 let summaries = context
215 .rewrite(root, 0, old_logical_bytes, final_size)
216 .await
217 .map_err(|error| OperationFailure::new(error, context.work))?;
218 let new_root = context
219 .finish_root(summaries)
220 .await
221 .map_err(|error| OperationFailure::new(error, context.work))?;
222 context
223 .visited
224 .release(&mut context.allocations)
225 .map_err(|error| OperationFailure::new(error.into(), context.work))?;
226 context
227 .allocations
228 .release(plan.live_allocation_bytes)
229 .map_err(|error| OperationFailure::new(error.into(), context.work))?;
230 Ok(ExtentMutationReceipt {
231 root: new_root,
232 logical_bytes: final_size,
233 work: context.work,
234 })
235}
236
237fn validate_request(
238 root: ObjectId,
239 mutations: &[ExtentMutation],
240 maximum_mutations: u32,
241 limits: DecodeLimits,
242) -> Result<(), ExtentMutationFailure> {
243 let error = if root.kind != ObjectKind::ExtentPage {
244 Some(ExtentMutationError::WrongRootKind)
245 } else if mutations.is_empty() {
246 Some(ExtentMutationError::Empty)
247 } else if maximum_mutations == 0
248 || usize::try_from(maximum_mutations).map_or(true, |maximum| mutations.len() > maximum)
249 {
250 Some(ExtentMutationError::TooManyMutations)
251 } else if !limits.page_limits_valid(2) {
252 Some(ExtentMutationError::InvalidLimits)
253 } else {
254 None
255 };
256 match error {
257 Some(error) => Err(OperationFailure::before_work(error)),
258 None => Ok(()),
259 }
260}
261
262fn compile_patch_plan(
263 old_size: u64,
264 mutations: &[ExtentMutation],
265 budget: WorkBudget,
266) -> Result<CompiledPatchPlan, ExtentMutationFailure> {
267 let (final_size, raw_count, mut work) = admit_patch_plan(old_size, mutations, budget)?;
268 if raw_count == 0 {
269 return Ok(CompiledPatchPlan {
270 patches: Vec::new(),
271 final_size,
272 work,
273 live_allocation_bytes: 0,
274 });
275 }
276 reserve_compile_scratch(raw_count, work, budget)?;
277 let mut live_bytes = 0_u64;
278 let mut raw = allocate_vec::<Patch>(raw_count, &mut work, budget, &mut live_bytes)?;
279 let mut size = old_size;
280 for mutation in mutations {
281 charge_items(&mut work, budget, 1)?;
282 match mutation {
283 ExtentMutation::Replace {
284 offset,
285 length,
286 kind,
287 extend: _,
288 } => {
289 let end = offset.checked_add(*length).ok_or_else(|| {
290 OperationFailure::new(ExtentMutationError::RangeOverflow, work)
291 })?;
292 if *offset > size {
293 raw.push(Patch {
294 offset: size,
295 end: *offset,
296 kind: ExtentKind::Hole,
297 });
298 }
299 raw.push(Patch {
300 offset: *offset,
301 end,
302 kind: kind.clone(),
303 });
304 size = size.max(end);
305 }
306 ExtentMutation::Resize { logical_bytes } => {
307 if *logical_bytes != size {
308 let start = size.min(*logical_bytes);
309 let end = size.max(*logical_bytes);
310 raw.push(Patch {
311 offset: start,
312 end,
313 kind: ExtentKind::Hole,
314 });
315 size = *logical_bytes;
316 }
317 }
318 }
319 }
320 let patches = normalize_raw_patches(&raw, final_size, &mut work, budget, &mut live_bytes)?;
321 let live_allocation_bytes = bytes_for::<Patch>(patches.capacity(), work)?;
322 Ok(CompiledPatchPlan {
323 patches,
324 final_size,
325 work,
326 live_allocation_bytes,
327 })
328}
329
330fn admit_patch_plan(
331 old_size: u64,
332 mutations: &[ExtentMutation],
333 budget: WorkBudget,
334) -> Result<(u64, usize, WorkCounters), ExtentMutationFailure> {
335 let mut size = old_size;
336 let mut raw_count = 0_usize;
337 let mut work = WorkCounters::default();
338 for mutation in mutations {
339 charge_items(&mut work, budget, 1)?;
340 match mutation {
341 ExtentMutation::Replace {
342 offset,
343 length,
344 kind,
345 extend,
346 } => {
347 if *length == 0 {
348 return Err(OperationFailure::new(ExtentMutationError::EmptyRange, work));
349 }
350 validate_kind(kind, *length).map_err(|error| OperationFailure::new(error, work))?;
351 let end = offset.checked_add(*length).ok_or_else(|| {
352 OperationFailure::new(ExtentMutationError::RangeOverflow, work)
353 })?;
354 if end > size && !extend {
355 return Err(OperationFailure::new(
356 ExtentMutationError::OutsideFile,
357 work,
358 ));
359 }
360 raw_count = raw_count
361 .checked_add(usize::from(*offset > size) + 1)
362 .ok_or_else(|| {
363 OperationFailure::new(ExtentMutationError::RangeOverflow, work)
364 })?;
365 size = size.max(end);
366 }
367 ExtentMutation::Resize { logical_bytes } => {
368 raw_count = raw_count
369 .checked_add(usize::from(*logical_bytes != size))
370 .ok_or_else(|| {
371 OperationFailure::new(ExtentMutationError::RangeOverflow, work)
372 })?;
373 size = *logical_bytes;
374 }
375 }
376 }
377 Ok((size, raw_count, work))
378}
379
380fn reserve_compile_scratch(
381 raw_count: usize,
382 work: WorkCounters,
383 budget: WorkBudget,
384) -> Result<(), ExtentMutationFailure> {
385 let coordinates = raw_count
386 .checked_mul(2)
387 .ok_or_else(|| OperationFailure::new(ExtentMutationError::RangeOverflow, work))?;
388 let intervals = coordinates.saturating_sub(1);
389 let capacities = [
390 bytes_for::<Patch>(raw_count, work)?,
391 bytes_for::<u64>(coordinates, work)?,
392 bytes_for::<u64>(coordinates, work)?,
393 bytes_for::<Option<usize>>(intervals, work)?,
394 bytes_for::<usize>(intervals + 1, work)?,
395 bytes_for::<Patch>(intervals, work)?,
396 ];
397 let bytes = capacities.into_iter().try_fold(0_u64, |total, bytes| {
398 total
399 .checked_add(bytes)
400 .ok_or_else(|| OperationFailure::new(ExtentMutationError::RangeOverflow, work))
401 })?;
402 let mut reserved = work
403 .checked_add(WorkCounters {
404 allocation_operations: 6,
405 ..WorkCounters::default()
406 })
407 .map_err(|error| OperationFailure::new(error.into(), work))?;
408 reserved.peak_allocation_bytes = reserved.peak_allocation_bytes.max(bytes);
409 reserved
410 .verify(budget)
411 .map_err(|error| OperationFailure::new(error.into(), work))
412}
413
414fn bytes_for<T>(count: usize, work: WorkCounters) -> Result<u64, ExtentMutationFailure> {
415 count
416 .checked_mul(size_of::<T>())
417 .map(crate::foundation::usize_to_u64)
418 .ok_or_else(|| OperationFailure::new(ExtentMutationError::RangeOverflow, work))
419}
420
421fn allocate_vec<T>(
422 capacity: usize,
423 work: &mut WorkCounters,
424 budget: WorkBudget,
425 live_bytes: &mut u64,
426) -> Result<Vec<T>, ExtentMutationFailure> {
427 let requested = bytes_for::<T>(capacity, *work)?;
428 let next_live = live_bytes
429 .checked_add(requested)
430 .ok_or_else(|| OperationFailure::new(ExtentMutationError::RangeOverflow, *work))?;
431 let mut prospective = work
432 .checked_add(WorkCounters {
433 allocation_operations: u64::from(capacity != 0),
434 ..WorkCounters::default()
435 })
436 .map_err(|error| OperationFailure::new(error.into(), *work))?;
437 prospective.peak_allocation_bytes = prospective.peak_allocation_bytes.max(next_live);
438 prospective
439 .verify(budget)
440 .map_err(|error| OperationFailure::new(error.into(), *work))?;
441 let mut values = Vec::new();
442 values
443 .try_reserve_exact(capacity)
444 .map_err(|_| OperationFailure::new(ExtentMutationError::AllocationFailed, prospective))?;
445 let actual = bytes_for::<T>(values.capacity(), prospective)?;
446 *live_bytes = live_bytes
447 .checked_add(actual)
448 .ok_or_else(|| OperationFailure::new(ExtentMutationError::RangeOverflow, prospective))?;
449 prospective.peak_allocation_bytes = prospective.peak_allocation_bytes.max(*live_bytes);
450 prospective
451 .verify(budget)
452 .map_err(|error| OperationFailure::new(error.into(), prospective))?;
453 *work = prospective;
454 Ok(values)
455}
456
457fn charge_items(
458 work: &mut WorkCounters,
459 budget: WorkBudget,
460 count: u64,
461) -> Result<(), ExtentMutationFailure> {
462 let prospective = work
463 .checked_add(WorkCounters {
464 items_examined: count,
465 ..WorkCounters::default()
466 })
467 .map_err(|error| OperationFailure::new(error.into(), *work))?;
468 prospective
469 .verify(budget)
470 .map_err(|error| OperationFailure::new(error.into(), *work))?;
471 *work = prospective;
472 Ok(())
473}
474
475fn charge_copied_bytes(
476 work: &mut WorkCounters,
477 budget: WorkBudget,
478 bytes: u64,
479) -> Result<(), ExtentMutationFailure> {
480 let prospective = work
481 .checked_add(WorkCounters {
482 bytes_copied: bytes,
483 ..WorkCounters::default()
484 })
485 .map_err(|error| OperationFailure::new(error.into(), *work))?;
486 prospective
487 .verify(budget)
488 .map_err(|error| OperationFailure::new(error.into(), *work))?;
489 *work = prospective;
490 Ok(())
491}
492
493fn normalize_raw_patches(
494 raw: &[Patch],
495 final_size: u64,
496 work: &mut WorkCounters,
497 budget: WorkBudget,
498 live_bytes: &mut u64,
499) -> Result<Vec<Patch>, ExtentMutationFailure> {
500 let coordinate_capacity = raw
501 .len()
502 .checked_mul(2)
503 .ok_or_else(|| OperationFailure::new(ExtentMutationError::RangeOverflow, *work))?;
504 let mut coordinates = allocate_vec::<u64>(coordinate_capacity, work, budget, live_bytes)?;
505 for patch in raw {
506 charge_items(work, budget, 1)?;
507 let start = patch.offset.min(final_size);
508 let end = patch.end.min(final_size);
509 if start < end {
510 coordinates.push(start);
511 coordinates.push(end);
512 }
513 }
514 if coordinates.is_empty() {
515 return Ok(Vec::new());
516 }
517 let mut scratch = allocate_vec::<u64>(coordinates.len(), work, budget, live_bytes)?;
518 radix_sort(&mut coordinates, &mut scratch, work, budget)?;
519 deduplicate_coordinates(&mut coordinates, work, budget)?;
520 let interval_count = coordinates.len().saturating_sub(1);
521 let mut assignments = allocate_vec::<Option<usize>>(interval_count, work, budget, live_bytes)?;
522 charge_items(
523 work,
524 budget,
525 u64::try_from(interval_count).unwrap_or(u64::MAX),
526 )?;
527 assignments.resize(interval_count, None);
528 let mut parents = allocate_vec::<usize>(interval_count + 1, work, budget, live_bytes)?;
529 charge_items(
530 work,
531 budget,
532 u64::try_from(interval_count + 1).unwrap_or(u64::MAX),
533 )?;
534 parents.extend(0..=interval_count);
535 for (patch_index, patch) in raw.iter().enumerate().rev() {
536 charge_items(work, budget, 1)?;
537 let start = patch.offset.min(final_size);
538 let end = patch.end.min(final_size);
539 if start >= end {
540 continue;
541 }
542 let left = coordinate_index(&coordinates, start, work, budget)?;
543 let right = coordinate_index(&coordinates, end, work, budget)?;
544 let mut interval = find_next(&mut parents, left, work, budget)?;
545 #[allow(
546 clippy::indexing_slicing,
547 reason = "coordinate_index's binary search only ever returns an index < \
548 coordinates.len(), and coordinates.len() == interval_count + 1, so `right` \
549 is at most interval_count; the loop guard `interval < right` therefore \
550 keeps `interval < interval_count`, which is in bounds for \
551 `assignments` (len interval_count) and `parents` (len interval_count + 1)"
552 )]
553 while interval < right {
554 charge_items(work, budget, 1)?;
555 assignments[interval] = Some(patch_index);
556 let successor = find_next(&mut parents, interval + 1, work, budget)?;
557 parents[interval] = successor;
558 interval = successor;
559 }
560 }
561 let mut result = allocate_vec::<Patch>(interval_count, work, budget, live_bytes)?;
562 for (interval, assignment) in assignments.into_iter().enumerate() {
563 charge_items(work, budget, 1)?;
564 let Some(patch_index) = assignment else {
565 continue;
566 };
567 #[allow(
568 clippy::indexing_slicing,
569 reason = "patch_index came from raw.iter().enumerate() above, so it is always a \
570 valid index into raw"
571 )]
572 let source = &raw[patch_index];
573 #[allow(
574 clippy::indexing_slicing,
575 reason = "interval is a valid index into assignments (len interval_count == \
576 coordinates.len() - 1) via .enumerate() on assignments, so interval and \
577 interval + 1 both stay within coordinates bounds"
578 )]
579 let candidate = Patch {
580 offset: coordinates[interval],
581 end: coordinates[interval + 1],
582 kind: offset_kind(&source.kind, coordinates[interval] - source.offset)
583 .map_err(|error| OperationFailure::new(error, *work))?,
584 };
585 if let Some(previous) = result.last_mut()
586 && patch_continues(previous, &candidate)
587 .map_err(|error| OperationFailure::new(error, *work))?
588 {
589 previous.end = candidate.end;
590 } else {
591 result.push(candidate);
592 }
593 }
594 Ok(result)
595}
596
597fn radix_sort(
598 values: &mut Vec<u64>,
599 scratch: &mut Vec<u64>,
600 work: &mut WorkCounters,
601 budget: WorkBudget,
602) -> Result<(), ExtentMutationFailure> {
603 scratch.resize(values.len(), 0);
604 for byte in 0..8_u32 {
605 charge_items(
606 work,
607 budget,
608 u64::try_from(values.len())
609 .unwrap_or(u64::MAX)
610 .saturating_mul(2),
611 )?;
612 charge_copied_bytes(work, budget, bytes_for::<u64>(values.len(), *work)?)?;
613 let shift = byte * 8;
614 let mut counts = [0_usize; 256];
615 #[allow(
616 clippy::indexing_slicing,
617 reason = "the index is `usize::from(<u8>)`, always in 0..=255, matching the fixed \
618 256-element `counts` array exactly"
619 )]
620 for value in values.iter() {
621 counts[usize::from(((value >> shift) & 0xff) as u8)] += 1;
622 }
623 let mut offset = 0_usize;
624 for count in &mut counts {
625 let current = *count;
626 *count = offset;
627 offset += current;
628 }
629 #[allow(
630 clippy::indexing_slicing,
631 reason = "`bucket` is `usize::from(<u8>)`, always in 0..=255, so `counts[bucket]` is \
632 in bounds of the fixed 256-element array; `counts[bucket]` itself is a \
633 running offset seeded by the exclusive prefix sum computed just above from \
634 a histogram over exactly `values.len()` elements, incremented once per \
635 element placed, so it never reaches `values.len()` and stays in bounds of \
636 `scratch` (resized to `values.len()` at the top of this function)"
637 )]
638 for value in values.iter() {
639 let bucket = usize::from(((value >> shift) & 0xff) as u8);
640 scratch[counts[bucket]] = *value;
641 counts[bucket] += 1;
642 }
643 std::mem::swap(values, scratch);
644 }
645 Ok(())
646}
647
648fn deduplicate_coordinates(
649 values: &mut Vec<u64>,
650 work: &mut WorkCounters,
651 budget: WorkBudget,
652) -> Result<(), ExtentMutationFailure> {
653 charge_items(
654 work,
655 budget,
656 u64::try_from(values.len()).unwrap_or(u64::MAX),
657 )?;
658 let mut write = 1_usize;
659 #[allow(
660 clippy::indexing_slicing,
661 reason = "`read` ranges over `1..values.len()` so `values[read]` is always in bounds; \
662 `write` starts equal to `read` (both 1) and only increments together with \
663 `read` (once per loop iteration, in this same branch), so `write <= read` is a \
664 loop invariant, keeping `write - 1` and `write` in bounds too"
665 )]
666 for read in 1..values.len() {
667 if values[read] != values[write - 1] {
668 values[write] = values[read];
669 write += 1;
670 }
671 }
672 values.truncate(write);
673 Ok(())
674}
675
676fn coordinate_index(
677 values: &[u64],
678 target: u64,
679 work: &mut WorkCounters,
680 budget: WorkBudget,
681) -> Result<usize, ExtentMutationFailure> {
682 let mut left = 0_usize;
683 let mut right = values.len();
684 while left < right {
685 charge_items(work, budget, 1)?;
686 let middle = left + (right - left) / 2;
687 #[allow(
688 clippy::indexing_slicing,
689 reason = "binary-search invariant: the loop guard `left < right` with `right` \
690 initialized to values.len() and only ever narrowed to `middle` keeps \
691 `middle = left + (right - left) / 2` strictly within `[left, right)`, so \
692 `middle < values.len()` always holds here"
693 )]
694 let ordering = values[middle].cmp(&target);
695 match ordering {
696 std::cmp::Ordering::Less => left = middle + 1,
697 std::cmp::Ordering::Greater => right = middle,
698 std::cmp::Ordering::Equal => return Ok(middle),
699 }
700 }
701 Err(OperationFailure::new(
702 ExtentMutationError::PatchInvariant,
703 *work,
704 ))
705}
706
707#[allow(
708 clippy::indexing_slicing,
709 reason = "union-find domain invariant: `parents` is initialized in normalize_raw_patches as \
710 the identity permutation over `0..=interval_count` (`parents.extend(0..=interval_count)`), \
711 and both call sites there pass a `start` already shown to be `<= interval_count` \
712 (from coordinate_index's binary-search postcondition, or from `interval < right <= \
713 interval_count`). Every write to a `parents` slot (here, and in \
714 normalize_raw_patches) only ever stores a value returned by `find_next`, which by \
715 induction is always an existing valid index reached by following the chain from a \
716 valid `start`. So `root`, `current`, and every `parents[..]` entry stay within \
717 `0..parents.len()` for the lifetime of the vector"
718)]
719fn find_next(
720 parents: &mut [usize],
721 start: usize,
722 work: &mut WorkCounters,
723 budget: WorkBudget,
724) -> Result<usize, ExtentMutationFailure> {
725 let mut root = start;
726 while parents[root] != root {
727 charge_items(work, budget, 1)?;
728 root = parents[root];
729 }
730 let mut current = start;
731 while parents[current] != current {
732 charge_items(work, budget, 1)?;
733 let next = parents[current];
734 parents[current] = root;
735 current = next;
736 }
737 Ok(root)
738}
739
740fn patch_continues(left: &Patch, right: &Patch) -> Result<bool, ExtentMutationError> {
741 if left.end != right.offset {
742 return Ok(false);
743 }
744 let left_extent = Extent {
745 offset: left.offset,
746 length: left.end - left.offset,
747 kind: left.kind.clone(),
748 };
749 let right_extent = Extent {
750 offset: right.offset,
751 length: right.end - right.offset,
752 kind: right.kind.clone(),
753 };
754 equivalent_continuation(&left_extent, &right_extent)
755}
756
757fn validate_kind(kind: &ExtentKind, length: u64) -> Result<(), ExtentMutationError> {
758 if let ExtentKind::Content {
759 object,
760 object_offset,
761 } = kind
762 {
763 if object.kind != ObjectKind::Blob {
764 return Err(ExtentMutationError::WrongContentKind);
765 }
766 object_offset
767 .checked_add(length)
768 .ok_or(ExtentMutationError::RangeOverflow)?;
769 }
770 Ok(())
771}
772
773fn extent_leaf_chunk_end(
774 extents: &[Extent],
775 start: usize,
776 limits: DecodeLimits,
777) -> Result<(usize, u64), ExtentMutationError> {
778 extent_chunk_end(extents, start, limits, |extent| match extent.kind {
779 ExtentKind::Hole | ExtentKind::AllocatedZero => 8 + 8 + 1,
780 ExtentKind::Content { .. } => 8 + 8 + 1 + 32 + 8,
781 })
782}
783
784fn extent_internal_chunk_end(
785 children: &[Summary],
786 start: usize,
787 limits: DecodeLimits,
788) -> Result<(usize, u64), ExtentMutationError> {
789 extent_chunk_end(children, start, limits, |_| 8 + 8 + 32)
790}
791
792fn extent_chunk_end<T>(
793 items: &[T],
794 start: usize,
795 limits: DecodeLimits,
796 encoded_length: impl Fn(&T) -> usize,
797) -> Result<(usize, u64), ExtentMutationError> {
798 const PAGE_HEADER_BYTES: usize = 8 + 2 + 1 + 4;
799 let maximum_items = usize::try_from(limits.maximum_page_items)
800 .map_err(|_| ExtentMutationError::InvalidLimits)?;
801 let maximum_bytes = usize::try_from(limits.maximum_page_bytes)
802 .map_err(|_| ExtentMutationError::InvalidLimits)?;
803 let mut end = start;
804 let mut bytes = PAGE_HEADER_BYTES;
805 let mut examined = 0_u64;
806 while end < items.len() && end - start < maximum_items {
807 examined = examined.saturating_add(1);
808 #[allow(
809 clippy::indexing_slicing,
810 reason = "the loop guard `end < items.len()` directly establishes `end` is in bounds \
811 at this point in the loop body"
812 )]
813 let next = bytes
814 .checked_add(encoded_length(&items[end]))
815 .ok_or(ExtentMutationError::PageItemTooLarge)?;
816 if next > maximum_bytes {
817 if end == start {
818 return Err(ExtentMutationError::PageItemTooLarge);
819 }
820 break;
821 }
822 bytes = next;
823 end += 1;
824 }
825 if end == start {
826 return Err(ExtentMutationError::PageItemTooLarge);
827 }
828 Ok((end, examined))
829}
830
831impl<S: crate::AsyncObjectStore> Context<'_, S> {
832 async fn rewrite(
833 &mut self,
834 page_id: ObjectId,
835 original_first: u64,
836 original_end: u64,
837 output_end: u64,
838 ) -> Result<Vec<Summary>, ExtentMutationError> {
839 let mut stack = Vec::new();
840 let mut request = NodeRequest {
841 page: page_id,
842 original_first,
843 original_end,
844 output_end,
845 height: 1,
846 };
847 'traverse: loop {
848 let entered = self.enter_node(request).await?;
849 let mut result = match entered {
850 EnteredNode::Complete(result) => result,
851 EnteredNode::Internal(mut frame) => {
852 if let Some(next) = self.advance_frame(&mut frame)? {
853 stack.push(frame);
854 request = next;
855 continue;
856 }
857 self.finish_frame(frame).await?
858 }
859 };
860 loop {
861 let Some(mut frame) = stack.pop() else {
862 return Ok(result);
863 };
864 frame.rewritten.extend(result);
865 if let Some(next) = self.advance_frame(&mut frame)? {
866 stack.push(frame);
867 request = next;
868 continue 'traverse;
869 }
870 result = self.finish_frame(frame).await?;
871 }
872 }
873 }
874
875 async fn enter_node(
876 &mut self,
877 request: NodeRequest,
878 ) -> Result<EnteredNode, ExtentMutationError> {
879 if request.height > self.limits.maximum_page_height {
880 return Err(ExtentMutationError::HeightExceeded);
881 }
882 if request.output_end <= request.original_first {
883 return Ok(EnteredNode::Complete(Vec::new()));
884 }
885 self.maximum_seen_height = self.maximum_seen_height.max(request.height);
886 let visited = self.visited.insert(
887 request.page,
888 &mut self.allocations,
889 &mut self.work,
890 self.budget,
891 )?;
892 if !visited.inserted {
893 return Err(ExtentMutationError::CycleOrAlias);
894 }
895 let decoded = self.read_page(request.page).await?;
896 match decoded.page {
897 ExtentPage::Leaf(extents) => {
898 let result = self
899 .rewrite_leaf(
900 request.page,
901 extents,
902 request.original_first,
903 request.original_end,
904 request.output_end,
905 )
906 .await;
907 self.allocations.release(decoded.logical_bytes)?;
908 Ok(EnteredNode::Complete(result?))
909 }
910 ExtentPage::Internal(children) => {
911 validate_children(&children, request.original_first, request.original_end)?;
912 Ok(EnteredNode::Internal(InternalFrame {
913 original: request.page,
914 children,
915 original_first: request.original_first,
916 original_end: request.original_end,
917 output_end: request.output_end,
918 height: request.height,
919 next_child: 0,
920 rewritten: Vec::new(),
921 logical_bytes: decoded.logical_bytes,
922 }))
923 }
924 }
925 }
926
927 fn advance_frame(
928 &mut self,
929 frame: &mut InternalFrame,
930 ) -> Result<Option<NodeRequest>, ExtentMutationError> {
931 while frame.next_child < frame.children.len() {
932 let index = frame.next_child;
933 frame.next_child += 1;
934 #[allow(
935 clippy::indexing_slicing,
936 reason = "`index` is captured from `frame.next_child` right after the loop guard \
937 `frame.next_child < frame.children.len()` confirmed it in bounds, \
938 before it is incremented"
939 )]
940 let child = &frame.children[index];
941 if child.first_offset >= frame.output_end {
942 self.charge_items(1)?;
943 frame.next_child = frame.children.len();
944 break;
945 }
946 let mut child_output_end = child.end_offset.min(frame.output_end);
947 if index + 1 == frame.children.len() && frame.output_end > frame.original_end {
948 child_output_end = frame.output_end;
949 }
950 let (patch_touched, comparisons) =
951 has_overlapping_patch(&self.patches, child.first_offset, child_output_end);
952 self.charge_items(comparisons.saturating_add(1))?;
953 let touched = child_output_end != child.end_offset || patch_touched;
954 if touched {
955 return Ok(Some(NodeRequest {
956 page: child.page,
957 original_first: child.first_offset,
958 original_end: child.end_offset,
959 output_end: child_output_end,
960 height: frame
961 .height
962 .checked_add(1)
963 .ok_or(ExtentMutationError::HeightExceeded)?,
964 }));
965 }
966 frame.rewritten.push(Summary {
967 first: child.first_offset,
968 end: child.end_offset,
969 page: child.page,
970 });
971 }
972 Ok(None)
973 }
974
975 async fn finish_frame(
976 &mut self,
977 frame: InternalFrame,
978 ) -> Result<Vec<Summary>, ExtentMutationError> {
979 let result = if frame.rewritten.is_empty() {
980 Ok(Vec::new())
981 } else if unchanged(&frame.rewritten, &frame.children) {
982 Ok(vec![Summary {
983 first: frame.original_first,
984 end: frame.original_end,
985 page: frame.original,
986 }])
987 } else if frame.height == 1 && frame.rewritten.len() == 1 {
988 Ok(frame.rewritten)
989 } else {
990 self.write_internal_chunks(&frame.rewritten).await
991 };
992 self.allocations.release(frame.logical_bytes)?;
993 result
994 }
995
996 async fn rewrite_leaf(
997 &mut self,
998 original: ObjectId,
999 extents: Vec<Extent>,
1000 original_first: u64,
1001 original_end: u64,
1002 output_end: u64,
1003 ) -> Result<Vec<Summary>, ExtentMutationError> {
1004 validate_leaf(&extents, original_first, original_end)?;
1005 if output_end <= original_first {
1006 return Ok(Vec::new());
1007 }
1008 let original_extents = extents.clone();
1009 let mut rewritten = clip_and_grow(extents, output_end, original_end)?;
1010 let (first_patch, after_last_patch, comparisons) =
1011 overlapping_patch_range(&self.patches, original_first, output_end);
1012 self.work = self.work.checked_add(WorkCounters {
1013 items_examined: comparisons,
1014 ..WorkCounters::default()
1015 })?;
1016 self.work.verify(self.budget)?;
1017 #[allow(
1018 clippy::indexing_slicing,
1019 reason = "overlapping_patch_range returns (first, after_last.max(first), _), where \
1020 `first` and `after_last` both come from lower_bound's postcondition of \
1021 `0 <= left <= patches.len()`; the explicit `.max(first)` clamp guarantees \
1022 `first_patch <= after_last_patch <= self.patches.len()`, so this slice is \
1023 always in bounds"
1024 )]
1025 let (next, examined) =
1026 apply_patches_linear(rewritten, &self.patches[first_patch..after_last_patch])?;
1027 rewritten = next;
1028 self.work = self.work.checked_add(WorkCounters {
1029 items_examined: examined,
1030 ..WorkCounters::default()
1031 })?;
1032 self.work.verify(self.budget)?;
1033 coalesce(&mut rewritten)?;
1034 if rewritten == original_extents {
1035 return Ok(vec![Summary {
1036 first: original_first,
1037 end: original_end,
1038 page: original,
1039 }]);
1040 }
1041 self.write_leaf_chunks(&rewritten).await
1042 }
1043
1044 async fn write_leaf_chunks(
1045 &mut self,
1046 extents: &[Extent],
1047 ) -> Result<Vec<Summary>, ExtentMutationError> {
1048 let mut result = Vec::new();
1049 let mut start = 0_usize;
1050 #[allow(
1051 clippy::indexing_slicing,
1052 reason = "extent_leaf_chunk_end delegates to extent_chunk_end, which guarantees \
1053 `start <= end_index <= extents.len()` (the loop there only advances `end` \
1054 while `end < items.len()`) and returns `Err` rather than success when `end \
1055 == start` (see the `if end == start` check before its `Ok` return), so \
1056 `chunk` is always non-empty here and `chunk[0]` / `chunk[chunk.len() - 1]` \
1057 are in bounds"
1058 )]
1059 while start < extents.len() {
1060 let (end_index, examined) = extent_leaf_chunk_end(extents, start, self.limits)?;
1061 self.charge_items(examined)?;
1062 let chunk = &extents[start..end_index];
1063 let first = chunk[0].offset;
1064 let end = extent_end(&chunk[chunk.len() - 1])?;
1065 result.push(Summary {
1066 first,
1067 end,
1068 page: self.write_page(&ExtentPage::Leaf(chunk.to_vec())).await?,
1069 });
1070 start = end_index;
1071 }
1072 Ok(result)
1073 }
1074
1075 async fn write_internal_chunks(
1076 &mut self,
1077 children: &[Summary],
1078 ) -> Result<Vec<Summary>, ExtentMutationError> {
1079 let mut cursor = 0_usize;
1080 let mut chunks = 0_usize;
1081 while cursor < children.len() {
1082 let (end, examined) = extent_internal_chunk_end(children, cursor, self.limits)?;
1083 self.charge_items(examined)?;
1084 chunks += 1;
1085 cursor = end;
1086 }
1087 if children.len() > 1 && chunks >= children.len() {
1088 return Err(ExtentMutationError::PageItemTooLarge);
1089 }
1090 let mut result = Vec::new();
1091 let mut start = 0_usize;
1092 #[allow(
1093 clippy::indexing_slicing,
1094 reason = "extent_internal_chunk_end delegates to extent_chunk_end, which guarantees \
1095 `start <= end_index <= children.len()` (the loop there only advances `end` \
1096 while `end < items.len()`) and returns `Err` rather than success when `end \
1097 == start` (see the `if end == start` check before its `Ok` return), so \
1098 `chunk` is always non-empty here and `chunk[0]` / `chunk[chunk.len() - 1]` \
1099 are in bounds"
1100 )]
1101 while start < children.len() {
1102 let (end_index, examined) = extent_internal_chunk_end(children, start, self.limits)?;
1103 self.charge_items(examined)?;
1104 let chunk = &children[start..end_index];
1105 let first = chunk[0].first;
1106 let end = chunk[chunk.len() - 1].end;
1107 let page = ExtentPage::Internal(
1108 chunk
1109 .iter()
1110 .map(|child| ExtentChild {
1111 first_offset: child.first,
1112 end_offset: child.end,
1113 page: child.page,
1114 })
1115 .collect(),
1116 );
1117 result.push(Summary {
1118 first,
1119 end,
1120 page: self.write_page(&page).await?,
1121 });
1122 start = end_index;
1123 }
1124 Ok(result)
1125 }
1126
1127 async fn finish_root(
1128 &mut self,
1129 mut summaries: Vec<Summary>,
1130 ) -> Result<ObjectId, ExtentMutationError> {
1131 if summaries.is_empty() {
1132 return self.write_page(&ExtentPage::Leaf(Vec::new())).await;
1133 }
1134 let mut height = self.maximum_seen_height;
1135 while summaries.len() > 1 {
1136 height = height
1137 .checked_add(1)
1138 .ok_or(ExtentMutationError::HeightExceeded)?;
1139 if height > self.limits.maximum_page_height {
1140 return Err(ExtentMutationError::HeightExceeded);
1141 }
1142 summaries = self.write_internal_chunks(&summaries).await?;
1143 }
1144 #[allow(
1145 clippy::indexing_slicing,
1146 reason = "the early `if summaries.is_empty()` return above guarantees `summaries` \
1147 starts non-empty; write_internal_chunks preserves non-emptiness because \
1148 its output has one Summary per input chunk and every chunk it forms is \
1149 non-empty (see the `if end == start` guard in extent_chunk_end, which \
1150 write_internal_chunks relies on via extent_internal_chunk_end), so a \
1151 non-empty `summaries` can only stay non-empty across the `while \
1152 summaries.len() > 1` loop; combined with that loop's exit condition, \
1153 `summaries.len() == 1` here"
1154 )]
1155 Ok(summaries[0].page)
1156 }
1157
1158 async fn read_page(&mut self, page: ObjectId) -> Result<OwnedExtentPage, ExtentMutationError> {
1159 let prospective = self.work.checked_add(WorkCounters {
1160 page_reads: 1,
1161 ..WorkCounters::default()
1162 })?;
1163 let mut remaining = prospective.remaining(self.budget)?;
1164 remaining.peak_allocation_bytes = self
1165 .budget
1166 .peak_allocation_bytes
1167 .checked_sub(self.allocations.live_bytes())
1168 .ok_or(WorkError::Overflow)?;
1169 let receipt = match crate::AsyncObjectStore::read(
1170 self.store,
1171 page,
1172 self.limits.maximum_page_object_bytes(),
1173 remaining,
1174 self.cancellation,
1175 )
1176 .await
1177 {
1178 Ok(receipt) => receipt,
1179 Err(failure) => {
1180 self.work =
1181 merge_backend_work(prospective, *failure.work, self.allocations.live_bytes())?;
1182 return Err(ExtentMutationError::Storage(failure.error));
1183 }
1184 };
1185 self.work = merge_backend_work(prospective, receipt.work, self.allocations.live_bytes())?;
1186 self.work.verify(self.budget)?;
1187 let retained_bytes = match receipt.value.retention {
1188 ObjectReadRetention::Shared => 0,
1189 ObjectReadRetention::Owned { logical_bytes } => logical_bytes,
1190 };
1191 self.allocations
1192 .claim_bytes(retained_bytes, 0, &mut self.work, self.budget)?;
1193 let shape = extent_page_decode_shape(&receipt.value, self.limits)?;
1194 self.charge_items(u64::try_from(shape.items).unwrap_or(u64::MAX))?;
1195 let item_bytes = match shape.kind {
1196 DecodedPageKind::Leaf => size_of::<Extent>(),
1197 DecodedPageKind::Internal => size_of::<ExtentChild>(),
1198 };
1199 let logical_bytes = shape
1200 .items
1201 .checked_mul(item_bytes)
1202 .map(crate::foundation::usize_to_u64)
1203 .ok_or(ExtentMutationError::AllocationFailed)?;
1204 self.allocations.claim_bytes(
1205 logical_bytes,
1206 u64::from(logical_bytes != 0),
1207 &mut self.work,
1208 self.budget,
1209 )?;
1210 let decoded = match decode_extent_page(&receipt.value, self.limits) {
1211 Ok(decoded) => decoded,
1212 Err(error) => {
1213 self.allocations.release(logical_bytes)?;
1214 return Err(ExtentMutationError::Decode(error));
1215 }
1216 };
1217 self.allocations.release(retained_bytes)?;
1218 Ok(OwnedExtentPage {
1219 page: decoded,
1220 logical_bytes,
1221 })
1222 }
1223
1224 fn charge_items(&mut self, count: u64) -> Result<(), ExtentMutationError> {
1225 let prospective = self.work.checked_add(WorkCounters {
1226 items_examined: count,
1227 ..WorkCounters::default()
1228 })?;
1229 prospective.verify(self.budget)?;
1230 self.work = prospective;
1231 Ok(())
1232 }
1233
1234 async fn write_page(&mut self, page: &ExtentPage) -> Result<ObjectId, ExtentMutationError> {
1235 let encoded_length = extent_page_encoded_length(page, self.limits.maximum_page_items)?;
1236 let encoded_bytes = crate::foundation::usize_to_u64(encoded_length);
1237 let maximum_page_bytes = self.limits.maximum_page_object_bytes();
1238 if encoded_bytes > maximum_page_bytes {
1239 return Err(ExtentMutationError::Decode(
1240 CanonicalDecodeError::ObjectTooLarge {
1241 observed: encoded_bytes,
1242 maximum: maximum_page_bytes,
1243 },
1244 ));
1245 }
1246 self.work
1247 .checked_add(WorkCounters {
1248 bytes_encoded: encoded_bytes,
1249 ..WorkCounters::default()
1250 })?
1251 .verify(self.budget)?;
1252 self.allocations.claim_bytes(
1253 encoded_bytes,
1254 u64::from(encoded_bytes != 0),
1255 &mut self.work,
1256 self.budget,
1257 )?;
1258 let encoded = encode_extent_page(page, self.limits.maximum_page_items)?;
1259 if u64::try_from(encoded.capacity()).unwrap_or(u64::MAX) != encoded_bytes {
1260 return Err(ExtentMutationError::AllocationFailed);
1261 }
1262 self.work = self.work.checked_add(WorkCounters {
1263 bytes_encoded: encoded_bytes,
1264 ..WorkCounters::default()
1265 })?;
1266 self.work.verify(self.budget)?;
1267 let hashed_work = self.work.checked_add(WorkCounters {
1268 bytes_hashed: encoded_bytes
1269 .checked_add(OBJECT_DIGEST_ENVELOPE_BYTES)
1270 .ok_or(ExtentMutationError::RangeOverflow)?,
1271 ..WorkCounters::default()
1272 })?;
1273 hashed_work.verify(self.budget)?;
1274 let object = ObjectId {
1275 kind: ObjectKind::ExtentPage,
1276 digest: object_digest(ObjectKind::ExtentPage, &encoded),
1277 };
1278 self.work = hashed_work;
1279 let prospective = self.work.checked_add(WorkCounters {
1280 page_writes: 1,
1281 ..WorkCounters::default()
1282 })?;
1283 let remaining = prospective.remaining(self.budget)?;
1284 let receipt = match crate::AsyncObjectStore::put(
1285 self.store,
1286 object,
1287 Bytes::from(encoded),
1288 remaining,
1289 self.cancellation,
1290 )
1291 .await
1292 {
1293 Ok(receipt) => receipt,
1294 Err(failure) => {
1295 self.allocations.release(encoded_bytes)?;
1296 self.work =
1297 merge_backend_work(prospective, *failure.work, self.allocations.live_bytes())?;
1298 return Err(ExtentMutationError::Storage(failure.error));
1299 }
1300 };
1301 self.work = prospective.checked_add(receipt.work)?;
1302 self.work.verify(self.budget)?;
1303 self.allocations.release(encoded_bytes)?;
1304 Ok(object)
1305 }
1306}
1307
1308fn merge_backend_work(
1309 prior: WorkCounters,
1310 mut backend: WorkCounters,
1311 live_bytes: u64,
1312) -> Result<WorkCounters, WorkError> {
1313 let simultaneous_peak = live_bytes
1314 .checked_add(backend.peak_allocation_bytes)
1315 .ok_or(WorkError::Overflow)?;
1316 backend.peak_allocation_bytes = 0;
1317 let mut merged = prior.checked_add(backend)?;
1318 merged.peak_allocation_bytes = merged.peak_allocation_bytes.max(simultaneous_peak);
1319 Ok(merged)
1320}
1321
1322fn clip_and_grow(
1323 extents: Vec<Extent>,
1324 output_end: u64,
1325 original_end: u64,
1326) -> Result<Vec<Extent>, ExtentMutationError> {
1327 let mut result = Vec::new();
1328 for mut extent in extents {
1329 if extent.offset >= output_end {
1330 break;
1331 }
1332 let end = extent_end(&extent)?.min(output_end);
1333 extent.length = end - extent.offset;
1334 result.push(extent);
1335 }
1336 if output_end > original_end {
1337 result.push(Extent {
1338 offset: original_end,
1339 length: output_end - original_end,
1340 kind: ExtentKind::Hole,
1341 });
1342 }
1343 Ok(result)
1344}
1345
1346fn apply_patches_linear(
1347 extents: Vec<Extent>,
1348 patches: &[Patch],
1349) -> Result<(Vec<Extent>, u64), ExtentMutationError> {
1350 let capacity = extents
1351 .len()
1352 .checked_add(patches.len().saturating_mul(2))
1353 .ok_or(ExtentMutationError::RangeOverflow)?;
1354 let mut result = Vec::with_capacity(capacity);
1355 let mut patch_index = 0;
1356 let mut examined = 0_u64;
1357 for extent in extents {
1358 examined = examined.saturating_add(1);
1359 let extent_end = extent_end(&extent)?;
1360 let mut cursor = extent.offset;
1361 while patches
1362 .get(patch_index)
1363 .is_some_and(|patch| patch.end <= cursor)
1364 {
1365 patch_index += 1;
1366 examined = examined.saturating_add(1);
1367 }
1368 while let Some(patch) = patches.get(patch_index) {
1369 examined = examined.saturating_add(1);
1370 if patch.offset >= extent_end {
1371 break;
1372 }
1373 if cursor < patch.offset {
1374 let unchanged_end = patch.offset.min(extent_end);
1375 result.push(Extent {
1376 offset: cursor,
1377 length: unchanged_end - cursor,
1378 kind: offset_kind(&extent.kind, cursor - extent.offset)?,
1379 });
1380 cursor = unchanged_end;
1381 }
1382 let replacement_start = cursor.max(patch.offset);
1383 let replacement_end = extent_end.min(patch.end);
1384 if replacement_start < replacement_end {
1385 result.push(Extent {
1386 offset: replacement_start,
1387 length: replacement_end - replacement_start,
1388 kind: offset_kind(&patch.kind, replacement_start - patch.offset)?,
1389 });
1390 cursor = replacement_end;
1391 }
1392 if patch.end <= cursor {
1393 patch_index += 1;
1394 } else {
1395 break;
1396 }
1397 }
1398 if cursor < extent_end {
1399 result.push(Extent {
1400 offset: cursor,
1401 length: extent_end - cursor,
1402 kind: offset_kind(&extent.kind, cursor - extent.offset)?,
1403 });
1404 }
1405 }
1406 Ok((result, examined))
1407}
1408
1409fn coalesce(extents: &mut Vec<Extent>) -> Result<(), ExtentMutationError> {
1410 let mut result: Vec<Extent> = Vec::with_capacity(extents.len());
1411 for extent in extents.drain(..) {
1412 if let Some(previous) = result.last_mut()
1413 && equivalent_continuation(previous, &extent)?
1414 {
1415 previous.length = previous
1416 .length
1417 .checked_add(extent.length)
1418 .ok_or(ExtentMutationError::RangeOverflow)?;
1419 continue;
1420 }
1421 result.push(extent);
1422 }
1423 *extents = result;
1424 Ok(())
1425}
1426
1427fn equivalent_continuation(left: &Extent, right: &Extent) -> Result<bool, ExtentMutationError> {
1428 if extent_end(left)? != right.offset {
1429 return Ok(false);
1430 }
1431 Ok(match (&left.kind, &right.kind) {
1432 (ExtentKind::Hole, ExtentKind::Hole)
1433 | (ExtentKind::AllocatedZero, ExtentKind::AllocatedZero) => true,
1434 (
1435 ExtentKind::Content {
1436 object: left_object,
1437 object_offset: left_offset,
1438 },
1439 ExtentKind::Content {
1440 object: right_object,
1441 object_offset: right_offset,
1442 },
1443 ) => {
1444 left_object == right_object
1445 && left_offset.checked_add(left.length) == Some(*right_offset)
1446 }
1447 _ => false,
1448 })
1449}
1450
1451fn offset_kind(kind: &ExtentKind, delta: u64) -> Result<ExtentKind, ExtentMutationError> {
1452 match kind {
1453 ExtentKind::Content {
1454 object,
1455 object_offset,
1456 } => Ok(ExtentKind::Content {
1457 object: *object,
1458 object_offset: object_offset
1459 .checked_add(delta)
1460 .ok_or(ExtentMutationError::RangeOverflow)?,
1461 }),
1462 value => Ok(value.clone()),
1463 }
1464}
1465
1466fn validate_leaf(
1467 extents: &[Extent],
1468 expected_first: u64,
1469 expected_end: u64,
1470) -> Result<(), ExtentMutationError> {
1471 if expected_first == expected_end && extents.is_empty() {
1472 return Ok(());
1473 }
1474 if extents.first().map(|extent| extent.offset) != Some(expected_first)
1475 || extents.last().map(extent_end).transpose()? != Some(expected_end)
1476 {
1477 return Err(ExtentMutationError::ChildBoundsMismatch);
1478 }
1479 Ok(())
1480}
1481
1482fn validate_children(
1483 children: &[ExtentChild],
1484 expected_first: u64,
1485 expected_end: u64,
1486) -> Result<(), ExtentMutationError> {
1487 if children.first().map(|child| child.first_offset) != Some(expected_first)
1488 || children.last().map(|child| child.end_offset) != Some(expected_end)
1489 {
1490 return Err(ExtentMutationError::ChildBoundsMismatch);
1491 }
1492 Ok(())
1493}
1494
1495fn unchanged(summaries: &[Summary], children: &[ExtentChild]) -> bool {
1496 summaries.len() == children.len()
1497 && summaries.iter().zip(children).all(|(left, right)| {
1498 left.first == right.first_offset
1499 && left.end == right.end_offset
1500 && left.page == right.page
1501 })
1502}
1503
1504fn overlapping_patch_range(patches: &[Patch], start: u64, end: u64) -> (usize, usize, u64) {
1505 let (first, first_comparisons) = lower_bound(patches, |patch| patch.end <= start);
1506 let (after_last, last_comparisons) = lower_bound(patches, |patch| patch.offset < end);
1507 (
1508 first,
1509 after_last.max(first),
1510 first_comparisons.saturating_add(last_comparisons),
1511 )
1512}
1513
1514fn has_overlapping_patch(patches: &[Patch], start: u64, end: u64) -> (bool, u64) {
1515 let (first, after_last, comparisons) = overlapping_patch_range(patches, start, end);
1516 (first < after_last, comparisons)
1517}
1518
1519fn lower_bound(patches: &[Patch], before: impl Fn(&Patch) -> bool) -> (usize, u64) {
1520 let mut left = 0;
1521 let mut right = patches.len();
1522 let mut comparisons = 0_u64;
1523 while left < right {
1524 comparisons = comparisons.saturating_add(1);
1525 let middle = left + (right - left) / 2;
1526 #[allow(
1527 clippy::indexing_slicing,
1528 reason = "binary-search invariant: the loop guard `left < right` with `right` \
1529 initialized to patches.len() and only ever narrowed to `middle` keeps \
1530 `middle = left + (right - left) / 2` strictly within `[left, right)`, so \
1531 `middle < patches.len()` always holds here"
1532 )]
1533 let below = before(&patches[middle]);
1534 if below {
1535 left = middle + 1;
1536 } else {
1537 right = middle;
1538 }
1539 }
1540 (left, comparisons)
1541}
1542
1543fn extent_end(extent: &Extent) -> Result<u64, ExtentMutationError> {
1544 extent
1545 .offset
1546 .checked_add(extent.length)
1547 .ok_or(ExtentMutationError::RangeOverflow)
1548}
1549
1550#[derive(Debug, Error)]
1552pub enum ExtentMutationError {
1553 #[error("extent mutation batch is empty")]
1555 Empty,
1556 #[error("extent mutation count exceeds its bound")]
1558 TooManyMutations,
1559 #[error("extent mutation root has the wrong kind")]
1561 WrongRootKind,
1562 #[error("extent mutation limits are invalid")]
1564 InvalidLimits,
1565 #[error("extent mutation item exceeds its page byte bound")]
1567 PageItemTooLarge,
1568 #[error("extent mutation range is empty")]
1570 EmptyRange,
1571 #[error("extent mutation range overflowed")]
1573 RangeOverflow,
1574 #[error("extent mutation exceeds logical file length")]
1576 OutsideFile,
1577 #[error("extent mutation content is not a blob")]
1579 WrongContentKind,
1580 #[error("extent mutation normalized patch map is inconsistent")]
1582 PatchInvariant,
1583 #[error("extent mutation scratch allocation failed")]
1585 AllocationFailed,
1586 #[error("extent mutation exceeds its height bound")]
1588 HeightExceeded,
1589 #[error("extent mutation encountered a cycle or alias")]
1591 CycleOrAlias,
1592 #[error("extent mutation child bounds mismatch")]
1594 ChildBoundsMismatch,
1595 #[error(transparent)]
1597 Storage(#[from] ObjectStoreError),
1598 #[error(transparent)]
1600 Decode(#[from] CanonicalDecodeError),
1601 #[error(transparent)]
1603 Work(#[from] WorkError),
1604}
1605
1606impl From<AllocationError> for ExtentMutationError {
1607 fn from(error: AllocationError) -> Self {
1608 match error {
1609 AllocationError::Work(error) => Self::Work(error),
1610 AllocationError::Overflow | AllocationError::ReleaseInvariant => {
1611 Self::Work(WorkError::Overflow)
1612 }
1613 AllocationError::InvalidCapacity
1614 | AllocationError::CapacityExceeded
1615 | AllocationError::AllocationFailed => Self::AllocationFailed,
1616 }
1617 }
1618}
1619
1620#[cfg(all(test, feature = "memory"))]
1621#[path = "tests/extent_mutation.rs"]
1622mod tests;