1use super::{
4 DecodeLimits, ExtentMutation, ExtentMutationError, ExtentMutationOptions,
5 apply_extent_mutations_async, plan_extent_range_async,
6};
7use crate::cancellation::CancellationToken;
8use crate::performance::{OperationFailure, WorkBudget, WorkCounters, WorkError};
9use crate::storage::{ByteRange, ObjectId, ObjectKind};
10use std::mem::size_of;
11use thiserror::Error;
12
13#[derive(Clone, Debug, Eq, PartialEq)]
15pub struct ExtentCloneReceipt {
16 pub root: ObjectId,
18 pub logical_bytes: u64,
20 pub work: WorkCounters,
22}
23
24pub type ExtentCloneFailure = OperationFailure<ExtentCloneError>;
26
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub struct ExtentCloneRequest {
30 pub source_root: ObjectId,
32 pub source_logical_bytes: u64,
34 pub source_range: ByteRange,
36 pub destination_root: ObjectId,
38 pub destination_logical_bytes: u64,
40 pub destination_offset: u64,
42 pub maximum_spans: u32,
44 pub maximum_mutations: u32,
46}
47
48pub fn clone_extent_range<S: crate::ImmediateObjectStore>(
61 store: &S,
62 request: ExtentCloneRequest,
63 limits: DecodeLimits,
64 budget: WorkBudget,
65) -> Result<ExtentCloneReceipt, ExtentCloneFailure> {
66 let cancellation = CancellationToken::new();
67 crate::async_storage::poll_immediate(clone_extent_range_async(
68 store,
69 request,
70 limits,
71 budget,
72 &cancellation,
73 ))
74}
75
76pub async fn clone_extent_range_async<S: crate::AsyncObjectStore>(
84 store: &S,
85 request: ExtentCloneRequest,
86 limits: DecodeLimits,
87 budget: WorkBudget,
88 cancellation: &CancellationToken,
89) -> Result<ExtentCloneReceipt, ExtentCloneFailure> {
90 validate_request(request, limits)?;
91 let plan = plan_extent_range_async(
92 store,
93 super::ExtentRangeRequest {
94 root: request.source_root,
95 file_size: request.source_logical_bytes,
96 range: request.source_range,
97 maximum_spans: request.maximum_spans,
98 limits,
99 budget,
100 },
101 cancellation,
102 )
103 .await
104 .map_err(|failure| OperationFailure::new(failure.error.into(), *failure.work))?;
105 let allocation_bytes = u64::try_from(plan.spans.len())
106 .unwrap_or(u64::MAX)
107 .saturating_mul(u64::try_from(size_of::<ExtentMutation>()).unwrap_or(u64::MAX));
108 let simultaneous_plan_allocation = plan
109 .retained_allocation_bytes
110 .checked_add(allocation_bytes)
111 .ok_or_else(|| {
112 OperationFailure::new(ExtentCloneError::Work(WorkError::Overflow), plan.work)
113 })?;
114 let mut work = plan
115 .work
116 .checked_add(WorkCounters {
117 allocation_operations: 1,
118 items_examined: u64::try_from(plan.spans.len()).unwrap_or(u64::MAX),
119 ..WorkCounters::default()
120 })
121 .map_err(|error| OperationFailure::new(error.into(), plan.work))?;
122 work.peak_allocation_bytes = work.peak_allocation_bytes.max(simultaneous_plan_allocation);
123 work.verify(budget)
124 .map_err(|error| OperationFailure::new(error.into(), plan.work))?;
125 let mut mutations = Vec::new();
126 if mutations.try_reserve_exact(plan.spans.len()).is_err() {
127 return Err(OperationFailure::new(
128 ExtentCloneError::AllocationFailed,
129 work,
130 ));
131 }
132 for span in plan.spans {
133 let relative = span
134 .offset
135 .checked_sub(request.source_range.offset)
136 .ok_or_else(|| OperationFailure::new(ExtentCloneError::RangeOverflow, work))?;
137 let offset = request
138 .destination_offset
139 .checked_add(relative)
140 .ok_or_else(|| OperationFailure::new(ExtentCloneError::RangeOverflow, work))?;
141 mutations.push(ExtentMutation::Replace {
142 offset,
143 length: span.length,
144 kind: span.kind,
145 extend: true,
146 });
147 }
148 let mut remaining = work
149 .remaining(budget)
150 .map_err(|error| OperationFailure::new(error.into(), work))?;
151 remaining.peak_allocation_bytes = budget
152 .peak_allocation_bytes
153 .checked_sub(allocation_bytes)
154 .ok_or_else(|| OperationFailure::new(ExtentCloneError::Work(WorkError::Overflow), work))?;
155 let receipt = apply_extent_mutations_async(
156 store,
157 request.destination_root,
158 request.destination_logical_bytes,
159 &mutations,
160 ExtentMutationOptions {
161 maximum_mutations: request.maximum_mutations,
162 limits,
163 budget: remaining,
164 },
165 cancellation,
166 )
167 .await
168 .map_err(|failure| failure.map_with_prior_work(work, Into::into))?;
169 let destination_peak = allocation_bytes
170 .checked_add(receipt.work.peak_allocation_bytes)
171 .ok_or_else(|| OperationFailure::new(ExtentCloneError::Work(WorkError::Overflow), work))?;
172 let mut destination_work = receipt.work;
173 destination_work.peak_allocation_bytes = 0;
174 work = work
175 .checked_add(destination_work)
176 .map_err(|error| OperationFailure::new(error.into(), work))?;
177 work.peak_allocation_bytes = work.peak_allocation_bytes.max(destination_peak);
178 work.verify(budget)
179 .map_err(|error| OperationFailure::new(error.into(), work))?;
180 Ok(ExtentCloneReceipt {
181 root: receipt.root,
182 logical_bytes: receipt.logical_bytes,
183 work,
184 })
185}
186
187fn validate_request(
188 request: ExtentCloneRequest,
189 limits: DecodeLimits,
190) -> Result<(), ExtentCloneFailure> {
191 let error = if request.source_root.kind != ObjectKind::ExtentPage
192 || request.destination_root.kind != ObjectKind::ExtentPage
193 {
194 Some(ExtentCloneError::WrongRootKind)
195 } else if request.source_range.length == 0 {
196 Some(ExtentCloneError::EmptyRange)
197 } else if request
198 .source_range
199 .offset
200 .checked_add(request.source_range.length)
201 .is_none()
202 || request
203 .destination_offset
204 .checked_add(request.source_range.length)
205 .is_none()
206 {
207 Some(ExtentCloneError::RangeOverflow)
208 } else if request.maximum_spans == 0
209 || request.maximum_mutations < request.maximum_spans
210 || !limits.page_limits_valid(2)
211 {
212 Some(ExtentCloneError::InvalidLimits)
213 } else {
214 None
215 };
216 match error {
217 Some(error) => Err(OperationFailure::before_work(error)),
218 None => Ok(()),
219 }
220}
221
222#[derive(Debug, Error)]
224pub enum ExtentCloneError {
225 #[error("range clone root has the wrong object kind")]
227 WrongRootKind,
228 #[error("range clone is empty")]
230 EmptyRange,
231 #[error("range clone overflowed")]
233 RangeOverflow,
234 #[error("range clone limits are invalid")]
236 InvalidLimits,
237 #[error("range clone scratch allocation failed")]
239 AllocationFailed,
240 #[error(transparent)]
242 Source(#[from] super::ExtentReadError),
243 #[error(transparent)]
245 Destination(#[from] ExtentMutationError),
246 #[error(transparent)]
248 Work(#[from] WorkError),
249}
250
251#[cfg(all(test, feature = "memory"))]
252#[path = "tests/extent_clone.rs"]
253mod tests;