| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| use crate::buffer::{Reader, Writer}; |
| use crate::config::Config; |
| use std::collections::HashMap; |
| use std::mem; |
| |
| use crate::error::Error; |
| use crate::meta::MetaString; |
| use crate::resolver::meta_resolver::{MetaReaderResolver, MetaWriterResolver}; |
| use crate::resolver::meta_string_resolver::{MetaStringReaderResolver, MetaStringWriterResolver}; |
| use crate::resolver::{RefReader, RefWriter}; |
| use crate::resolver::{TypeInfo, TypeResolver}; |
| use crate::serializer::StructSerializer; |
| use crate::type_id as types; |
| use crate::TypeId; |
| use std::rc::Rc; |
| |
| /// Thread-local context cache with fast path for single Fory instance. |
| /// Uses (cached_id, context) for O(1) access when using same Fory instance repeatedly. |
| /// Falls back to HashMap for multiple Fory instances per thread. |
| pub struct ContextCache<T> { |
| /// Fast path: cached context for the most recently used Fory instance |
| cached_id: u64, |
| cached_context: Option<Box<T>>, |
| /// Slow path: HashMap for other Fory instances |
| others: HashMap<u64, Box<T>>, |
| } |
| |
| impl<T> ContextCache<T> { |
| pub fn new() -> Self { |
| ContextCache { |
| cached_id: u64::MAX, |
| cached_context: None, |
| others: HashMap::new(), |
| } |
| } |
| |
| #[inline(always)] |
| pub fn get_or_insert(&mut self, id: u64, create: impl FnOnce() -> Box<T>) -> &mut T { |
| if self.cached_id == id { |
| // Fast path: same Fory instance as last time |
| return self.cached_context.as_mut().unwrap(); |
| } |
| |
| // Check if we need to swap with cached |
| if self.cached_context.is_some() { |
| // Move current cached to others |
| let old_id = self.cached_id; |
| let old_context = self.cached_context.take().unwrap(); |
| self.others.insert(old_id, old_context); |
| } |
| |
| // Get or create context for new id |
| let context = self.others.remove(&id).unwrap_or_else(create); |
| self.cached_id = id; |
| self.cached_context = Some(context); |
| self.cached_context.as_mut().unwrap() |
| } |
| |
| /// Like `get_or_insert`, but the create closure returns a Result. |
| /// This allows error handling during context creation without pre-fetching resources. |
| #[inline(always)] |
| pub fn get_or_insert_result<E>( |
| &mut self, |
| id: u64, |
| create: impl FnOnce() -> Result<Box<T>, E>, |
| ) -> Result<&mut T, E> { |
| if self.cached_id == id { |
| // Fast path: same Fory instance as last time |
| return Ok(self.cached_context.as_mut().unwrap()); |
| } |
| |
| // Check if we need to swap with cached |
| if self.cached_context.is_some() { |
| // Move current cached to others |
| let old_id = self.cached_id; |
| let old_context = self.cached_context.take().unwrap(); |
| self.others.insert(old_id, old_context); |
| } |
| |
| // Get or create context for new id |
| let context = match self.others.remove(&id) { |
| Some(ctx) => ctx, |
| None => create()?, |
| }; |
| self.cached_id = id; |
| self.cached_context = Some(context); |
| Ok(self.cached_context.as_mut().unwrap()) |
| } |
| } |
| |
| impl<T> Default for ContextCache<T> { |
| fn default() -> Self { |
| Self::new() |
| } |
| } |
| |
| /// Serialization state container used on a single thread at a time. |
| /// Sharing the same instance across threads simultaneously causes undefined behavior. |
| #[allow(clippy::needless_lifetimes)] |
| pub struct WriteContext<'a> { |
| // Replicated environment fields (direct access, no Arc indirection for flags) |
| type_resolver: TypeResolver, |
| compatible: bool, |
| share_meta: bool, |
| compress_string: bool, |
| xlang: bool, |
| check_struct_version: bool, |
| track_ref: bool, |
| |
| // Context-specific fields |
| default_writer: Option<Writer<'a>>, |
| pub writer: Writer<'a>, |
| meta_resolver: MetaWriterResolver, |
| meta_string_resolver: MetaStringWriterResolver, |
| pub ref_writer: RefWriter, |
| } |
| |
| #[allow(clippy::needless_lifetimes)] |
| impl<'a> WriteContext<'a> { |
| pub fn new(type_resolver: TypeResolver, config: Config) -> WriteContext<'a> { |
| WriteContext { |
| type_resolver, |
| compatible: config.compatible, |
| share_meta: config.share_meta, |
| compress_string: config.compress_string, |
| xlang: config.xlang, |
| check_struct_version: config.check_struct_version, |
| track_ref: config.track_ref, |
| default_writer: None, |
| writer: Writer::from_buffer(Self::get_leak_buffer()), |
| meta_resolver: MetaWriterResolver::default(), |
| meta_string_resolver: MetaStringWriterResolver::default(), |
| ref_writer: RefWriter::new(), |
| } |
| } |
| |
| #[inline(always)] |
| fn get_leak_buffer() -> &'static mut Vec<u8> { |
| Box::leak(Box::new(vec![])) |
| } |
| |
| #[inline(always)] |
| pub fn attach_writer(&mut self, writer: Writer<'a>) { |
| let old = mem::replace(&mut self.writer, writer); |
| self.default_writer = Some(old); |
| } |
| |
| #[inline(always)] |
| pub fn detach_writer(&mut self) { |
| let default = mem::take(&mut self.default_writer); |
| self.writer = default.unwrap(); |
| } |
| |
| /// Get type resolver |
| #[inline(always)] |
| pub fn get_type_resolver(&self) -> &TypeResolver { |
| &self.type_resolver |
| } |
| |
| #[inline(always)] |
| pub fn get_provider_type_info( |
| &self, |
| provider_type_id: &std::any::TypeId, |
| ) -> Result<Rc<TypeInfo>, Error> { |
| self.type_resolver.get_provider_type_info(provider_type_id) |
| } |
| |
| #[inline(always)] |
| pub fn get_target_type_info( |
| &self, |
| target_type_id: &std::any::TypeId, |
| ) -> Result<Rc<TypeInfo>, Error> { |
| self.type_resolver.get_target_type_info(target_type_id) |
| } |
| |
| /// Check if compatible mode is enabled |
| #[inline(always)] |
| pub fn is_compatible(&self) -> bool { |
| self.compatible |
| } |
| |
| /// Check if meta sharing is enabled |
| #[inline(always)] |
| pub fn is_share_meta(&self) -> bool { |
| self.share_meta |
| } |
| |
| /// Check if string compression is enabled |
| #[inline(always)] |
| pub fn is_compress_string(&self) -> bool { |
| self.compress_string |
| } |
| |
| /// Check if xlang mode is enabled |
| #[inline(always)] |
| pub fn is_xlang(&self) -> bool { |
| self.xlang |
| } |
| |
| /// Check if class version checking is enabled |
| #[inline(always)] |
| pub fn is_check_struct_version(&self) -> bool { |
| self.check_struct_version |
| } |
| |
| /// Check if reference tracking is enabled |
| #[inline(always)] |
| pub fn is_track_ref(&self) -> bool { |
| self.track_ref |
| } |
| |
| /// Write type meta inline using streaming protocol. |
| /// Writes index marker with LSB indicating new type or reference. |
| #[inline(always)] |
| pub fn write_type_meta(&mut self, type_id: std::any::TypeId) -> Result<(), Error> { |
| self.meta_resolver |
| .write_type_meta(&mut self.writer, type_id, &self.type_resolver) |
| } |
| |
| /// Write generated struct type info without Rust TypeId hash lookups. |
| #[inline(always)] |
| pub fn write_struct_type_info<T: StructSerializer>(&mut self) -> Result<(), Error> { |
| let rust_type_id = std::any::TypeId::of::<T>(); |
| let type_index = T::type_index(); |
| let type_id = self.type_resolver.get_type_id_by_index(type_index)?; |
| match type_id { |
| TypeId::STRUCT | TypeId::ENUM | TypeId::EXT | TypeId::TYPED_UNION => { |
| self.writer.write_u8(type_id as u8); |
| let user_type_id = self |
| .type_resolver |
| .get_user_type_id_by_index(&rust_type_id, type_index)?; |
| self.writer.write_var_u32(user_type_id); |
| } |
| TypeId::COMPATIBLE_STRUCT | TypeId::NAMED_COMPATIBLE_STRUCT => { |
| self.writer.write_u8(type_id as u8); |
| self.meta_resolver.write_type_meta_fast( |
| &mut self.writer, |
| rust_type_id, |
| type_index, |
| &self.type_resolver, |
| )?; |
| } |
| TypeId::NAMED_ENUM | TypeId::NAMED_EXT | TypeId::NAMED_STRUCT | TypeId::NAMED_UNION |
| if self.is_share_meta() => |
| { |
| self.writer.write_u8(type_id as u8); |
| self.meta_resolver.write_type_meta_fast( |
| &mut self.writer, |
| rust_type_id, |
| type_index, |
| &self.type_resolver, |
| )?; |
| } |
| _ => { |
| self.write_provider_type_info(type_id as u32, rust_type_id)?; |
| } |
| } |
| Ok(()) |
| } |
| |
| pub fn write_provider_type_info( |
| &mut self, |
| wire_type_id: u32, |
| provider_type_id: std::any::TypeId, |
| ) -> Result<Rc<TypeInfo>, Error> { |
| let type_info = self |
| .type_resolver |
| .get_provider_type_info(&provider_type_id)?; |
| self.write_resolved_type_info(wire_type_id, type_info) |
| } |
| |
| pub fn write_target_type_info( |
| &mut self, |
| wire_type_id: u32, |
| target_type_id: std::any::TypeId, |
| ) -> Result<Rc<TypeInfo>, Error> { |
| let type_info = self.type_resolver.get_target_type_info(&target_type_id)?; |
| self.write_resolved_type_info(wire_type_id, type_info) |
| } |
| |
| #[doc(hidden)] |
| #[inline(always)] |
| pub fn write_resolved_type_info( |
| &mut self, |
| wire_type_id: u32, |
| type_info: Rc<TypeInfo>, |
| ) -> Result<Rc<TypeInfo>, Error> { |
| if types::is_internal_type(wire_type_id) { |
| self.writer.write_u8(wire_type_id as u8); |
| return Ok(type_info); |
| } |
| let wire_type_id = type_info.get_type_id(); |
| let namespace = type_info.get_namespace(); |
| let type_name = type_info.get_type_name(); |
| self.writer.write_u8(wire_type_id as u8); |
| // should be compiled to jump table generation |
| match wire_type_id { |
| TypeId::ENUM | TypeId::STRUCT | TypeId::EXT | TypeId::TYPED_UNION => { |
| let user_type_id = type_info.get_user_type_id(); |
| self.writer.write_var_u32(user_type_id); |
| } |
| TypeId::COMPATIBLE_STRUCT | TypeId::NAMED_COMPATIBLE_STRUCT => { |
| self.meta_resolver |
| .write_resolved_type_meta(&mut self.writer, &type_info)?; |
| } |
| TypeId::NAMED_ENUM | TypeId::NAMED_EXT | TypeId::NAMED_STRUCT | TypeId::NAMED_UNION => { |
| if self.is_share_meta() { |
| self.meta_resolver |
| .write_resolved_type_meta(&mut self.writer, &type_info)?; |
| } else { |
| self.write_meta_string_bytes(namespace)?; |
| self.write_meta_string_bytes(type_name)?; |
| } |
| } |
| _ => { |
| // default case: do nothing |
| } |
| } |
| Ok(type_info) |
| } |
| |
| #[inline(always)] |
| pub fn write_meta_string_bytes(&mut self, ms: Rc<MetaString>) -> Result<(), Error> { |
| self.meta_string_resolver |
| .write_meta_string_bytes(&mut self.writer, ms) |
| } |
| |
| #[inline(always)] |
| pub fn reset(&mut self) { |
| self.meta_resolver.reset(); |
| self.meta_string_resolver.reset(); |
| self.ref_writer.reset(); |
| } |
| } |
| |
| #[allow(clippy::needless_lifetimes)] |
| impl<'a> Drop for WriteContext<'a> { |
| fn drop(&mut self) { |
| unsafe { |
| drop(Box::from_raw(self.writer.bf)); |
| } |
| } |
| } |
| |
| /// Deserialization state container used on a single thread at a time. |
| /// Sharing the same instance across threads simultaneously causes undefined behavior. |
| pub struct ReadContext<'a> { |
| // Replicated environment fields (direct access, no Arc indirection for flags) |
| type_resolver: TypeResolver, |
| config: Config, |
| compatible: bool, |
| share_meta: bool, |
| xlang: bool, |
| max_dyn_depth: u32, |
| check_struct_version: bool, |
| check_string_read: bool, |
| pub(crate) remaining_graph_memory_bytes: usize, |
| pub(crate) remaining_unbacked_container_items: usize, |
| |
| // Context-specific fields |
| pub reader: Reader<'a>, |
| pub meta_resolver: MetaReaderResolver, |
| meta_string_resolver: MetaStringReaderResolver, |
| pub ref_reader: RefReader, |
| current_depth: u32, |
| } |
| |
| impl<'a> ReadContext<'a> { |
| pub fn new(type_resolver: TypeResolver, config: Config) -> ReadContext<'a> { |
| ReadContext { |
| type_resolver, |
| config: config.clone(), |
| compatible: config.compatible, |
| share_meta: config.share_meta, |
| xlang: config.xlang, |
| max_dyn_depth: config.max_dyn_depth, |
| check_struct_version: config.check_struct_version, |
| check_string_read: config.check_string_read, |
| remaining_graph_memory_bytes: 0, |
| remaining_unbacked_container_items: 0, |
| reader: Reader::default(), |
| meta_resolver: MetaReaderResolver::default(), |
| meta_string_resolver: MetaStringReaderResolver::default(), |
| ref_reader: RefReader::new(), |
| current_depth: 0, |
| } |
| } |
| |
| /// Get type resolver |
| #[inline(always)] |
| pub fn get_type_resolver(&self) -> &TypeResolver { |
| &self.type_resolver |
| } |
| |
| /// Check if compatible mode is enabled |
| #[inline(always)] |
| pub fn is_compatible(&self) -> bool { |
| self.compatible |
| } |
| |
| /// Check if meta sharing is enabled |
| #[inline(always)] |
| pub fn is_share_meta(&self) -> bool { |
| self.share_meta |
| } |
| |
| /// Check if xlang mode is enabled |
| #[inline(always)] |
| pub fn is_xlang(&self) -> bool { |
| self.xlang |
| } |
| |
| /// Check if class version checking is enabled |
| #[inline(always)] |
| pub fn is_check_struct_version(&self) -> bool { |
| self.check_struct_version |
| } |
| |
| /// Check if UTF-8 string payload validation is enabled. |
| #[inline(always)] |
| pub fn is_check_string_read(&self) -> bool { |
| self.check_string_read |
| } |
| |
| /// Get maximum dynamic depth |
| #[inline(always)] |
| pub fn max_dyn_depth(&self) -> u32 { |
| self.max_dyn_depth |
| } |
| |
| #[inline(always)] |
| pub fn attach_reader(&mut self, reader: Reader<'a>) { |
| self.reader = reader; |
| } |
| |
| #[inline(always)] |
| #[doc(hidden)] |
| pub fn reserve_graph_memory(&mut self, bytes: usize) -> Result<(), Error> { |
| let remaining = self.remaining_graph_memory_bytes; |
| if bytes > remaining { |
| return Err(graph_memory_exceeded( |
| bytes, |
| remaining, |
| self.config.max_graph_memory_bytes, |
| )); |
| } |
| self.remaining_graph_memory_bytes = remaining - bytes; |
| Ok(()) |
| } |
| |
| #[inline(always)] |
| #[doc(hidden)] |
| pub fn remaining_unbacked_container_items(&self) -> usize { |
| self.remaining_unbacked_container_items |
| } |
| |
| #[inline(always)] |
| #[doc(hidden)] |
| pub fn reserve_unbacked_container_items(&mut self, items: usize) -> Result<(), Error> { |
| let remaining = self.remaining_unbacked_container_items; |
| if items > remaining { |
| return Err(unbacked_container_items_exceeded(items, remaining)); |
| } |
| self.remaining_unbacked_container_items = remaining - items; |
| Ok(()) |
| } |
| |
| #[inline(always)] |
| pub fn detach_reader(&mut self) -> Reader<'_> { |
| mem::take(&mut self.reader) |
| } |
| |
| #[inline(always)] |
| pub fn get_type_info_by_index(&self, type_index: usize) -> Result<&Rc<TypeInfo>, Error> { |
| self.meta_resolver.get(type_index).ok_or_else(|| { |
| Error::type_error(format!("TypeInfo not found for type index: {}", type_index)) |
| }) |
| } |
| |
| #[inline(always)] |
| pub fn get_meta(&self, type_index: usize) -> Result<&Rc<TypeInfo>, Error> { |
| self.get_type_info_by_index(type_index) |
| } |
| |
| /// Read type meta inline using streaming protocol. |
| /// Returns the TypeInfo for this type. |
| #[inline(always)] |
| pub fn read_type_meta(&mut self) -> Result<Rc<TypeInfo>, Error> { |
| self.meta_resolver |
| .read_type_meta(&mut self.reader, &self.type_resolver, &self.config) |
| } |
| |
| #[inline(always)] |
| fn read_type_meta_for(&mut self, expected: &Rc<TypeInfo>) -> Result<Rc<TypeInfo>, Error> { |
| self.meta_resolver.read_type_meta_for( |
| &mut self.reader, |
| &self.type_resolver, |
| &self.config, |
| expected, |
| ) |
| } |
| |
| #[inline(always)] |
| fn read_struct_type_meta_for( |
| &mut self, |
| expected: &Rc<TypeInfo>, |
| ) -> Result<Rc<TypeInfo>, Error> { |
| self.meta_resolver.read_struct_type_meta_for( |
| &mut self.reader, |
| &self.type_resolver, |
| &self.config, |
| expected, |
| ) |
| } |
| |
| pub fn read_any_type_info(&mut self) -> Result<Rc<TypeInfo>, Error> { |
| self.read_any_type_info_with_expected(None, false) |
| } |
| |
| #[inline(always)] |
| pub(crate) fn read_type_info_for( |
| &mut self, |
| expected_target: std::any::TypeId, |
| ) -> Result<Rc<TypeInfo>, Error> { |
| let expected = self.type_resolver.get_target_type_info(&expected_target)?; |
| self.read_any_type_info_with_expected(Some(&expected), false) |
| } |
| |
| #[inline(always)] |
| pub(crate) fn read_struct_type_info_for( |
| &mut self, |
| expected_target: std::any::TypeId, |
| ) -> Result<Rc<TypeInfo>, Error> { |
| let expected = self.type_resolver.get_target_type_info(&expected_target)?; |
| self.read_any_type_info_with_expected(Some(&expected), true) |
| } |
| |
| fn read_any_type_info_with_expected( |
| &mut self, |
| expected: Option<&Rc<TypeInfo>>, |
| allow_structural_stub: bool, |
| ) -> Result<Rc<TypeInfo>, Error> { |
| let fory_type_id = self.reader.read_u8()? as u32; |
| // should be compiled to jump table generation |
| let type_info = match fory_type_id { |
| types::ENUM | types::STRUCT | types::EXT | types::TYPED_UNION => { |
| let user_type_id = self.reader.read_var_u32()?; |
| self.type_resolver |
| .get_user_type_info_by_id(user_type_id) |
| .ok_or_else(|| Error::type_error("ID harness not found"))? |
| } |
| types::COMPATIBLE_STRUCT | types::NAMED_COMPATIBLE_STRUCT => { |
| // Read type meta inline using streaming protocol |
| return match expected { |
| Some(expected) if allow_structural_stub => { |
| self.read_struct_type_meta_for(expected) |
| } |
| Some(expected) => self.read_type_meta_for(expected), |
| None => self.read_type_meta(), |
| }; |
| } |
| types::NAMED_ENUM | types::NAMED_EXT | types::NAMED_STRUCT | types::NAMED_UNION => { |
| if self.is_share_meta() { |
| // Read type meta inline using streaming protocol |
| return match expected { |
| Some(expected) if allow_structural_stub => { |
| self.read_struct_type_meta_for(expected) |
| } |
| Some(expected) => self.read_type_meta_for(expected), |
| None => self.read_type_meta(), |
| }; |
| } else { |
| self.read_named_type_info()? |
| } |
| } |
| _ => self |
| .type_resolver |
| .get_type_info_by_id(fory_type_id) |
| .ok_or_else(|| Error::type_error("ID harness not found"))?, |
| }; |
| if let Some(expected) = expected { |
| let expected_target = expected |
| .get_harness() |
| .target_type_id() |
| .ok_or_else(|| Error::type_error("expected TypeInfo has no concrete target"))?; |
| let resolved_target = type_info.get_harness().target_type_id(); |
| if resolved_target != Some(expected_target) |
| && !(allow_structural_stub && resolved_target.is_none()) |
| { |
| return Err(Error::type_error( |
| "resolved TypeInfo target does not match declared target", |
| )); |
| } |
| } |
| Ok(type_info) |
| } |
| |
| // Name decoding and resolver fallback allocate; keep them out of the common ID and compatible |
| // dispatch body without marking successful named dispatch as cold. |
| #[inline(never)] |
| fn read_named_type_info(&mut self) -> Result<Rc<TypeInfo>, Error> { |
| let namespace = self.read_meta_string()?.to_owned(); |
| let type_name = self.read_meta_string()?.to_owned(); |
| let rc_namespace = Rc::from(namespace.clone()); |
| let rc_type_name = Rc::from(type_name.clone()); |
| self.type_resolver |
| .get_type_info_by_meta_string_name(rc_namespace, rc_type_name) |
| .or_else(|| { |
| self.type_resolver |
| .get_type_info_by_name(namespace.original.as_str(), type_name.original.as_str()) |
| }) |
| .ok_or_else(|| { |
| Error::type_error(format!( |
| "Name harness not found: namespace='{}', type='{}'", |
| namespace.original, type_name.original |
| )) |
| }) |
| } |
| |
| #[inline(always)] |
| pub fn get_provider_type_info( |
| &self, |
| provider_type_id: &std::any::TypeId, |
| ) -> Result<Rc<TypeInfo>, Error> { |
| self.type_resolver.get_provider_type_info(provider_type_id) |
| } |
| |
| #[inline(always)] |
| pub fn get_target_type_info( |
| &self, |
| target_type_id: &std::any::TypeId, |
| ) -> Result<Rc<TypeInfo>, Error> { |
| self.type_resolver.get_target_type_info(target_type_id) |
| } |
| |
| #[inline(always)] |
| pub fn read_meta_string(&mut self) -> Result<&MetaString, Error> { |
| self.meta_string_resolver.read_meta_string(&mut self.reader) |
| } |
| |
| #[inline(always)] |
| pub fn inc_depth(&mut self) -> Result<(), Error> { |
| self.current_depth += 1; |
| if self.current_depth > self.max_dyn_depth() { |
| return Err(Error::depth_exceed(format!( |
| "Maximum dynamic object nesting depth ({}) exceeded. Current depth: {}. \ |
| This may indicate a circular reference or overly deep object graph. \ |
| Consider increasing max_dyn_depth if this is expected.", |
| self.max_dyn_depth(), |
| self.current_depth |
| ))); |
| } |
| Ok(()) |
| } |
| |
| #[inline(always)] |
| pub fn dec_depth(&mut self) { |
| // Nested readers decrement only after their child completed successfully. An error keeps |
| // the failed path's depth until the root reset owns all read-side cleanup. |
| self.current_depth = self.current_depth.saturating_sub(1); |
| } |
| |
| #[inline(always)] |
| pub fn reset(&mut self) { |
| self.meta_resolver.reset(); |
| self.meta_string_resolver.reset(); |
| self.ref_reader.reset(); |
| // Root reset is the only failure-cleanup owner for read depth. |
| self.current_depth = 0; |
| self.remaining_unbacked_container_items = 0; |
| } |
| } |
| |
| #[cold] |
| #[inline(never)] |
| fn graph_memory_exceeded(bytes: usize, remaining: usize, limit: usize) -> Error { |
| Error::invalid_data(format!( |
| "estimated graph memory request {} bytes exceeds max_graph_memory_bytes remaining budget {} bytes out of effective limit {} bytes", |
| bytes, remaining, limit |
| )) |
| } |
| |
| #[cold] |
| #[inline(never)] |
| fn unbacked_container_items_exceeded(items: usize, remaining: usize) -> Error { |
| Error::invalid_data(format!( |
| "container read work request {items} items exceeds max_unbacked_container_items remaining budget {remaining} items" |
| )) |
| } |