1use std::num::NonZeroUsize;
18use super::capacity::{Held, Pool, Refusal, Reservation};
21use super::manager::{Domain, ENTRY_OVERHEAD, Operation};
22use super::{
23 ObjectCatalog, ObjectEntry, ObjectPoint, ObjectReceipt, ObjectRevision, ObjectValue, ObjectVersion, ObjectVersionToken, StorageManager,
24 StorageSchema, StoredObject,
25};
26use crate::{Error, MemorySize, Result};
27use harmos_domain::catalog::Contains;
28use harmos_domain::model::storage::catalog::{decode_admitted, object_schema};
29use harmos_domain::model::storage::object_key;
30
31pub struct ObjectGroup<'a, C> {
40 manager: &'a StorageManager<C>,
41 first: u64,
42 count: usize,
43 allowance: usize,
44 used: usize,
45 schemas: Vec<StorageSchema>,
46 objects: Vec<ObjectEntry>,
47 receipts: Vec<ObjectReceipt>,
48}
49
50impl<C: ObjectCatalog> ObjectGroup<'_, C> {
57 pub fn remaining_bytes(&self) -> usize {
58 self.allowance - self.used
59 }
60
61 pub fn write<T: StoredObject>(&mut self, value: &T) -> Result<()>
62 where
63 C: Contains<T>,
64 {
65 if self.objects.len() == self.count {
66 return Err(invalid("object group contains too many objects"));
67 }
68 let settings = self.schema_settings()?;
69 let graph = crate::schema::graph::Schema::from_declaration(T::DEFINITION, &settings)
70 .map_err(|_| invalid("invalid object schema or preparation allowance"))?;
71 let (id, _) = T::definition();
72 if graph
73 .memory_size()
74 .checked_add(id.as_str().len())
75 .and_then(|bytes| bytes.checked_add(std::mem::size_of::<super::SchemaKey>()))
76 .is_none_or(|bytes| bytes > self.remaining_bytes())
77 {
78 return Err(invalid("object identity exceeds allowance"));
79 }
80 let schema = object_schema::<T>()?;
81 let metadata = self.metadata(&schema, &graph)?;
82 if metadata >= self.remaining_bytes() {
83 return Err(invalid("object metadata exceeds allowance"));
84 }
85 let limit = NonZeroUsize::new((self.remaining_bytes() - metadata) / 2).ok_or_else(|| invalid("object key exceeds allowance"))?;
86 let item = object_key(value.key(), limit)?;
87 let key_bytes = item.memory_size();
88 let length = value.encoded_len();
89 let bytes =
90 metadata.checked_add(key_bytes).and_then(|bytes| bytes.checked_add(length)).ok_or_else(|| invalid("object size overflow"))?;
91 if bytes > self.remaining_bytes() {
92 return Err(invalid("object payload exceeds allowance"));
93 }
94 let payload = value.encode_to_vec();
95 if payload.len() != length || payload.capacity() > self.remaining_bytes() - metadata - key_bytes {
96 return Err(invalid("object codec exceeded its reported allowance"));
97 }
98 self.append(ObjectValue { schema, item, payload }, graph, metadata + key_bytes)?;
99 Ok(())
100 }
101}
102
103impl<C> ObjectGroup<'_, C> {
104 pub(super) fn write_value(&mut self, value: &ObjectValue) -> Result<()> {
105 if self.objects.len() == self.count {
106 return Err(invalid("object group contains too many objects"));
107 }
108 if self.allowance - self.used < harmos_domain::model::storage::catalog::LOOKUP_MEMORY_BYTES {
110 return Err(invalid("object catalog lookup exceeds allowance"));
111 }
112 let definition = (self.manager.inner.object_definition)(&value.schema)?;
113 let graph = definition.graph(&self.schema_settings()?)?;
114 let metadata = self.metadata(&value.schema, &graph)?;
115 let verification =
116 (self.allowance - self.used).checked_sub(metadata).ok_or_else(|| invalid("object metadata exceeds allowance"))?;
117 definition.validate_admitted(value, self.manager.inner.settings.max_batch_bytes(), verification)?;
118 let retained = metadata
119 .checked_add(value.item.memory_size())
120 .and_then(|bytes| bytes.checked_add(value.payload.len()))
121 .ok_or_else(|| invalid("object size overflow"))?;
122 if retained > self.allowance - self.used {
123 return Err(invalid("object payload exceeds allowance"));
124 }
125 let value = value.clone();
126 let bytes = metadata + value.item.memory_size();
127 self.append(value, graph, bytes)
128 }
129
130 fn schema_settings(&self) -> Result<harmos_domain::config::SchemaSettings> {
131 let mut settings = harmos_domain::config::SchemaSettings::default();
132 settings.preparation_bytes = NonZeroUsize::new(settings.preparation_bytes.get().min(self.allowance - self.used))
133 .ok_or_else(|| invalid("object schema preparation exceeds allowance"))?;
134 Ok(settings)
135 }
136
137 fn metadata(&self, schema: &super::SchemaKey, graph: &crate::schema::graph::Schema) -> Result<usize> {
138 let descriptor = StorageSchema::resident_bytes(schema, graph, self.manager.inner.primary.history())?;
139 let clone = graph.clone_memory_bytes().map_err(|_| invalid("object schema size overflow"))?;
140 let retained = graph.memory_size().checked_sub(clone).ok_or_else(|| invalid("object schema clone exceeds retained allocation"))?;
141 let identity = self.manager.charge(&(schema, self.manager.inner.primary.history()), 4)?;
142 descriptor.checked_add(retained).and_then(|bytes| bytes.checked_add(identity)).ok_or_else(|| invalid("object size overflow"))
143 }
144
145 fn append(&mut self, value: ObjectValue, graph: crate::schema::graph::Schema, metadata: usize) -> Result<()> {
146 let bytes = metadata.checked_add(value.payload.capacity()).ok_or_else(|| invalid("object size overflow"))?;
147 if bytes > self.allowance - self.used {
148 return Err(invalid("object payload exceeds allowance"));
149 }
150 let sequence = self.first + (self.objects.len() * 2) as u64;
151 let descriptor = StorageSchema::new(self.manager.position(sequence), value.schema.clone(), graph);
152 let position = self.manager.position(sequence + 1);
153 let version = ObjectVersionToken::new(position.clone());
154 self.used += bytes;
155 self.schemas.push(descriptor);
156 self.objects.push(ObjectEntry { position: position.clone(), version: version.clone(), value });
157 self.receipts.push(ObjectReceipt { position, version });
158 Ok(())
159 }
160}
161
162impl<C: ObjectCatalog> StorageManager<C> {
163 pub async fn write_object<T: StoredObject>(&self, value: &T) -> Result<ObjectReceipt>
164 where
165 C: Contains<T>,
166 {
167 let receipts = self.write_object_group(self.object_admission_bytes(), NonZeroUsize::MIN, |group| group.write(value)).await?;
168 Ok(receipts.into_iter().next().unwrap())
169 }
170
171 pub async fn write_object_group(
172 &self, max_bytes: NonZeroUsize, count: NonZeroUsize, build: impl FnOnce(&mut ObjectGroup<'_, C>) -> Result<()> + Send,
173 ) -> Result<Vec<ObjectReceipt>> {
174 self.admit_object_group(max_bytes, count, None, build).await
175 }
176
177 pub async fn read_object<T: StoredObject>(&self, key: &T::Key, version: ObjectVersion) -> Result<Option<ObjectRevision<T>>>
178 where
179 C: Contains<T>,
180 {
181 let schema = object_schema::<T>()?;
182 let budget = self.inner.settings.scan_bytes();
183 let point = ObjectPoint { schema: schema.id.clone(), item: object_key(key, budget)?, version };
184 let saved = self.cursor_saved();
185 if let ObjectVersion::Exact(version) = &point.version {
186 version.history().check(&saved.history)?;
187 if version.revision().get() > saved.sequence {
188 return Ok(None);
189 }
190 }
191
192 let raw = budget.get().saturating_add(ENTRY_OVERHEAD);
195 let mut credit = raw;
196 let (object, _charge) = loop {
197 let held = Held::default();
198 let mut charge = Reservation::wait(&self.inner, Pool::Read, &held, credit, None).await.map_err(Refusal::into_error)?;
199 let snapshot = self.snapshot(&saved).await?;
200 let Some(object) = snapshot.read_object(&point).await? else {
201 return Ok(None);
202 };
203 if matches!(&point.version, ObjectVersion::Exact(version) if version != &object.version) {
204 return Err(invalid("backend returned a different object revision"));
205 }
206 let decoded = T::decoding_memory(&object.value.payload)
207 .map_err(|error| Error::backend("preflight object", crate::BackendFailure::Corrupt, error))?;
208 let total = raw.saturating_add(decoded);
209 match charge.grow(total.saturating_sub(charge.bytes()), None).await {
210 Ok(()) => break (object, charge),
211 Err(Refusal::Cycle) => credit = total,
212 Err(refusal) => return Err(refusal.into_error()),
213 }
214 };
215 let value = decode_admitted::<T>(&object.value, &schema)?;
216 Ok(Some(ObjectRevision { value, position: object.position, version: object.version }))
217 }
218}
219
220impl<C> StorageManager<C> {
221 pub(super) fn object_admission_bytes(&self) -> NonZeroUsize {
222 NonZeroUsize::new(
223 self.inner.settings.max_batch_bytes().get().saturating_mul(4).min(self.inner.settings.request.max_pending_write_bytes.get()),
224 )
225 .unwrap()
226 }
227
228 pub(super) async fn admit_object_group(
229 &self, max_bytes: NonZeroUsize, count: NonZeroUsize, mutation: Option<super::admissions::Mutation<'_>>,
230 build: impl FnOnce(&mut ObjectGroup<'_, C>) -> Result<()> + Send,
231 ) -> Result<Vec<ObjectReceipt>> {
232 let _writer = self.inner.object_writes.lock().await;
233 self.admit_object_group_locked(max_bytes, count, mutation, build).await
234 }
235
236 pub(super) async fn admit_object_group_locked(
237 &self, max_bytes: NonZeroUsize, count: NonZeroUsize, mutation: Option<super::admissions::Mutation<'_>>,
238 build: impl FnOnce(&mut ObjectGroup<'_, C>) -> Result<()> + Send,
239 ) -> Result<Vec<ObjectReceipt>> {
240 let positions = count.get().checked_mul(2).ok_or_else(|| invalid("object group count overflow"))?;
241 let mut receipts = None;
242 let last = self
243 .admit_mutation(Domain::Data, max_bytes.get(), positions, mutation, false, |first| {
244 let slots = count
245 .get()
246 .checked_mul(
247 std::mem::size_of::<StorageSchema>()
248 + std::mem::size_of::<ObjectEntry>()
249 + std::mem::size_of::<ObjectReceipt>()
250 + 2 * std::mem::size_of::<super::StoragePart<'_>>()
251 + 2 * ENTRY_OVERHEAD,
252 )
253 .ok_or_else(|| invalid("object group size overflow"))?;
254 if slots > max_bytes.get() {
255 return Err(invalid("object group metadata exceeds allowance"));
256 }
257 let mut group = ObjectGroup {
258 manager: self,
259 first,
260 count: count.get(),
261 allowance: max_bytes.get(),
262 used: slots,
263 schemas: Vec::with_capacity(count.get()),
264 objects: Vec::with_capacity(count.get()),
265 receipts: Vec::with_capacity(count.get()),
266 };
267 build(&mut group)?;
268 if group.objects.len() != count.get() {
269 return Err(invalid("object group count differs from admission"));
270 }
271 receipts = Some(group.receipts);
272 Ok(Operation::ObjectGroup { schemas: group.schemas, objects: group.objects, bytes: group.used })
273 })
274 .await?;
275 if let Some(receipts) = receipts {
276 return Ok(receipts);
277 }
278 let first = last.checked_sub(positions as u64 - 2).ok_or_else(|| invalid("invalid object group receipt"))?;
281 Ok((0..count.get())
282 .map(|index| {
283 let position = self.position(first + (index * 2) as u64);
284 ObjectReceipt { version: ObjectVersionToken::new(position.clone()), position }
285 })
286 .collect())
287 }
288}
289
290pub(super) fn invalid(reason: &'static str) -> crate::Error {
295 harmos_domain::model::storage::invalid(reason).into()
296}
297
298