diff --git a/Cargo.lock b/Cargo.lock index b5505020..cbbf53bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4026,6 +4026,12 @@ dependencies = [ "libc", ] +[[package]] +name = "self_cell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" + [[package]] name = "semver" version = "1.0.28" @@ -5162,6 +5168,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typed-path" version = "0.12.3" @@ -6023,6 +6035,7 @@ dependencies = [ "reqwest 0.12.28", "rquickjs", "rust_decimal", + "self_cell", "serde", "serde_json", "serde_path_to_error", @@ -6035,6 +6048,7 @@ dependencies = [ "thiserror 1.0.69", "tokio", "toml 0.8.23", + "typed-arena", "zen-expression", "zen-tmpl", "zen-types", diff --git a/core/engine/Cargo.toml b/core/engine/Cargo.toml index 7d2ffb6f..7780af89 100644 --- a/core/engine/Cargo.toml +++ b/core/engine/Cargo.toml @@ -38,6 +38,8 @@ swc_ts_fast_strip = "53.0.0" swc_common = "23" swc_ecma_parser = "41" swc_ecma_ast = "25" +typed-arena = "2" +self_cell = "1" [dev-dependencies] tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/core/engine/src/lib.rs b/core/engine/src/lib.rs index 3c277610..f4d2b495 100644 --- a/core/engine/src/lib.rs +++ b/core/engine/src/lib.rs @@ -120,7 +120,7 @@ // `deny` rather than `forbid` so `nodes::variable_json` can opt in: reading a // `RefCell` without a guard is the only way to hand jsonschema `'a` borrows. -#![deny(unsafe_code)] +#![forbid(unsafe_code)] #![deny(clippy::unwrap_used)] #![allow(clippy::module_inception)] diff --git a/core/engine/src/loader/cached.rs b/core/engine/src/loader/cached.rs index 417744f8..70f82f4c 100644 --- a/core/engine/src/loader/cached.rs +++ b/core/engine/src/loader/cached.rs @@ -54,15 +54,25 @@ impl DecisionLoader for CachedLoader { key: &'a str, ) -> Pin + 'a + Send>> { Box::pin(async move { - if let Ok(cache) = self.cache.read() { - if let Some(content) = cache.get(key) { - return Ok(content.clone()); - } - } + let cached = self + .cache + .read() + .ok() + .and_then(|cache| cache.get(key).cloned()); - let decision_content = prepared(&self.loader, self.loader.load(key).await?).await; - if let Ok(mut cache) = self.cache.write() { - cache.insert(key.to_string(), decision_content.clone()); + let loaded = match &cached { + Some(content) => content.clone(), + None => self.loader.load(key).await?, + }; + + let decision_content = prepared(&self.loader, loaded).await; + let unchanged = cached + .as_ref() + .is_some_and(|content| Arc::ptr_eq(content, &decision_content)); + if !unchanged { + if let Ok(mut cache) = self.cache.write() { + cache.insert(key.to_string(), decision_content.clone()); + } } Ok(decision_content) }) diff --git a/core/engine/src/nodes/context.rs b/core/engine/src/nodes/context.rs index 80b03c9a..320cac3b 100644 --- a/core/engine/src/nodes/context.rs +++ b/core/engine/src/nodes/context.rs @@ -2,6 +2,7 @@ use crate::nodes::definition::{NodeDataType, TraceDataType}; use crate::nodes::extensions::NodeHandlerExtensions; use crate::nodes::function::v2::function::Function; use crate::nodes::result::{NodeResponse, NodeResult}; +use crate::nodes::variable_json::{Guards, VariableNode}; use crate::nodes::NodeError; use crate::ZEN_CONFIG; use ahash::AHasher; @@ -39,6 +40,19 @@ where NodeData: NodeDataType, TraceData: TraceDataType, { + pub fn input_with_nodes(&self) -> Variable { + let Some(nodes) = &self.nodes else { + return self.input.shallow_clone(); + }; + let Variable::Object(object) = &self.input else { + return self.input.shallow_clone(); + }; + + let mut map = object.borrow().clone(); + map.insert(Variable::nodes_key(), nodes.clone()); + Variable::from_object(map) + } + pub fn from_base(base: NodeContextBase, data: NodeData) -> Self { Self { id: base.id, @@ -103,8 +117,9 @@ where .get_or_insert(hash, schema) .node_context(self)?; + let guards = Guards::default(); validator - .validate(value) + .validate(VariableNode::new(value, &guards)) .map_err(|err| ValidationErrorJson::from(err)) .node_context(self)?; diff --git a/core/engine/src/nodes/custom/mod.rs b/core/engine/src/nodes/custom/mod.rs index f875538e..89605f7d 100644 --- a/core/engine/src/nodes/custom/mod.rs +++ b/core/engine/src/nodes/custom/mod.rs @@ -21,7 +21,7 @@ impl NodeHandler for CustomNodeHandler { async fn handle(&self, ctx: NodeContext) -> NodeResult { let custom_node_request = CustomNodeRequest { - input: ctx.input.clone(), + input: ctx.input_with_nodes(), node: CustomDecisionNode { id: ctx.id.clone(), name: ctx.name.clone(), diff --git a/core/engine/src/nodes/function/v1/mod.rs b/core/engine/src/nodes/function/v1/mod.rs index 421690cf..7ad46df7 100644 --- a/core/engine/src/nodes/function/v1/mod.rs +++ b/core/engine/src/nodes/function/v1/mod.rs @@ -30,7 +30,7 @@ impl NodeHandler for FunctionV1NodeHandler { runtime.set_interrupt_handler(Some(interrupt_handler)); let mut script = Script::new(runtime.clone()); - let result_response = script.call(ctx.node.deref(), &ctx.input).await; + let result_response = script.call(ctx.node.deref(), &ctx.input_with_nodes()).await; runtime.set_interrupt_handler(None); diff --git a/core/engine/src/nodes/variable_json.rs b/core/engine/src/nodes/variable_json.rs index 34d87ada..edbf7d61 100644 --- a/core/engine/src/nodes/variable_json.rs +++ b/core/engine/src/nodes/variable_json.rs @@ -1,20 +1,66 @@ -// Safety: `Variable` is `Rc`-based, so reading through a `RefCell` without a -// guard is the only way to hand out `'a` borrows. Scoped to this module. -#![allow(unsafe_code)] - use std::borrow::Cow; +use std::cell::Ref; use jsonschema::json::{Array, Json, JsonNumber, Node, NodeIdentity, Object}; use jsonschema::JsonType; use rust_decimal::prelude::ToPrimitive; use rust_decimal::Decimal; +use self_cell::self_cell; use serde_json::Value; -use zen_types::variable::{Variable, VariableMap}; +use typed_arena::Arena; +use zen_types::variable::{MapIter, RcCell, Variable, VariableMap}; pub struct VariableJson; +type MapRef<'a> = Ref<'a, VariableMap>; +type VecRef<'a> = Ref<'a, Vec>; + +self_cell!( + struct MapGuard { + owner: RcCell, + + #[covariant] + dependent: MapRef, + } +); + +self_cell!( + struct VecGuard { + owner: RcCell>, + + #[covariant] + dependent: VecRef, + } +); + +pub struct Guards { + objects: Arena, + arrays: Arena, +} + +impl Default for Guards { + fn default() -> Self { + Self { + objects: Arena::new(), + arrays: Arena::new(), + } + } +} + +#[derive(Clone, Copy)] +pub struct VariableNode<'a> { + var: &'a Variable, + guards: &'a Guards, +} + +impl<'a> VariableNode<'a> { + pub fn new(var: &'a Variable, guards: &'a Guards) -> Self { + Self { var, guards } + } +} + impl Json for VariableJson { - type Node<'a> = &'a Variable; + type Node<'a> = VariableNode<'a>; type PreparedKey = Box; type StringBuffer = Variable; @@ -25,10 +71,11 @@ impl Json for VariableJson { fn with_string_node( buffer: &mut Variable, string: &str, - f: impl FnOnce(&Variable) -> T, + f: impl FnOnce(VariableNode<'_>) -> T, ) -> T { *buffer = Variable::String((string).into()); - f(buffer) + let guards = Guards::default(); + f(VariableNode::new(buffer, &guards)) } } @@ -36,11 +83,11 @@ pub struct VariableNumber(Decimal); impl JsonNumber for VariableNumber { fn as_u64(&self) -> Option { - self.0.to_u64() + self.0.is_integer().then(|| self.0.to_u64()).flatten() } fn as_i64(&self) -> Option { - self.0.to_i64() + self.0.is_integer().then(|| self.0.to_i64()).flatten() } fn as_f64(&self) -> Option { @@ -61,66 +108,69 @@ impl JsonNumber for VariableNumber { } } -/// # Safety -/// -/// Validation is a pure read: nothing in `jsonschema` holds a `Variable`, and no -/// node handler mutates the input while its schema is being checked. The `Rc` -/// keeps the cell alive for at least `'a`, so reading through the cell's pointer -/// cannot outlive the allocation and cannot race a `borrow_mut`. -#[inline] -unsafe fn cell_ref<'a, T: zen_types::rccell::Recycle>( - cell: &zen_types::rccell::RcCell, -) -> &'a T { - unsafe { cell.get_ref() } -} - -impl<'a> Node<'a, VariableJson> for &'a Variable { - type Object = &'a VariableMap; - type Array = &'a [Variable]; +impl<'a> Node<'a, VariableJson> for VariableNode<'a> { + type Object = ObjectNode<'a>; + type Array = ArrayNode<'a>; type Number = VariableNumber; - fn as_object(&self) -> Option<&'a VariableMap> { - match self { - Variable::Object(map) => Some(unsafe { cell_ref(map) }), - _ => None, - } + fn as_object(&self) -> Option> { + let Variable::Object(cell) = self.var else { + return None; + }; + + let guard = self + .guards + .objects + .alloc(MapGuard::new(cell.clone(), |cell| cell.borrow())); + Some(ObjectNode { + map: guard.borrow_dependent(), + guards: self.guards, + }) } - fn as_array(&self) -> Option<&'a [Variable]> { - match self { - Variable::Array(items) => Some(unsafe { cell_ref(items) }.as_slice()), - _ => None, - } + fn as_array(&self) -> Option> { + let Variable::Array(cell) = self.var else { + return None; + }; + + let guard = self + .guards + .arrays + .alloc(VecGuard::new(cell.clone(), |cell| cell.borrow())); + Some(ArrayNode { + items: guard.borrow_dependent().as_slice(), + guards: self.guards, + }) } fn as_string(&self) -> Option> { - match self { - Variable::String(string) => Some(Cow::Borrowed(string)), + match self.var { + Variable::String(string) => Some(Cow::Borrowed(string.as_str())), Variable::Dynamic(dynamic) => Some(Cow::Owned(dynamic.to_string())), _ => None, } } fn as_number(&self) -> Option { - match self { + match self.var { Variable::Number(number) => Some(VariableNumber(*number)), _ => None, } } fn as_boolean(&self) -> Option { - match self { + match self.var { Variable::Bool(boolean) => Some(*boolean), _ => None, } } fn is_null(&self) -> bool { - matches!(self, Variable::Null) + matches!(self.var, Variable::Null) } fn json_type(&self) -> JsonType { - match self { + match self.var { Variable::Null => JsonType::Null, Variable::Bool(_) => JsonType::Boolean, Variable::Number(_) => JsonType::Number, @@ -132,53 +182,91 @@ impl<'a> Node<'a, VariableJson> for &'a Variable { } fn to_value(&self) -> Cow<'a, Value> { - Cow::Owned(Variable::to_value(self)) + Cow::Owned(Variable::to_value(self.var)) } fn identity(&self) -> Option { Some(NodeIdentity::new( - std::ptr::from_ref::(*self) as usize + std::ptr::from_ref::(self.var) as usize )) } } -impl<'a> Object<'a, VariableJson> for &'a VariableMap { - type Node = &'a Variable; +pub struct ObjectNode<'a> { + map: &'a VariableMap, + guards: &'a Guards, +} + +impl<'a> Object<'a, VariableJson> for ObjectNode<'a> { + type Node = VariableNode<'a>; type MemberName = &'a str; type MembersIter = VariableMembersIter<'a>; fn len(&self) -> usize { - VariableMap::len(self) + self.map.len() } - fn get(&self, key: &Box) -> Option<&'a Variable> { - VariableMap::get_str(self, key) + fn get(&self, key: &Box) -> Option> { + self.map + .get_str(key) + .map(|var| VariableNode::new(var, self.guards)) } fn members(&self) -> VariableMembersIter<'a> { - VariableMembersIter(self.iter()) + VariableMembersIter { + iter: self.map.iter(), + guards: self.guards, + } } } -pub struct VariableMembersIter<'a>(zen_types::variable::MapIter<'a>); +pub struct VariableMembersIter<'a> { + iter: MapIter<'a>, + guards: &'a Guards, +} impl<'a> Iterator for VariableMembersIter<'a> { - type Item = (&'a str, &'a Variable); + type Item = (&'a str, VariableNode<'a>); fn next(&mut self) -> Option { - self.0.next().map(|(name, value)| (name.as_str(), value)) + self.iter + .next() + .map(|(name, value)| (name.as_str(), VariableNode::new(value, self.guards))) } } -impl<'a> Array<'a, VariableJson> for &'a [Variable] { - type Node = &'a Variable; - type ElementsIter = std::slice::Iter<'a, Variable>; +pub struct ArrayNode<'a> { + items: &'a [Variable], + guards: &'a Guards, +} + +impl<'a> Array<'a, VariableJson> for ArrayNode<'a> { + type Node = VariableNode<'a>; + type ElementsIter = VariableElementsIter<'a>; fn len(&self) -> usize { - <[Variable]>::len(self) + self.items.len() } - fn elements(&self) -> std::slice::Iter<'a, Variable> { - self.iter() + fn elements(&self) -> VariableElementsIter<'a> { + VariableElementsIter { + iter: self.items.iter(), + guards: self.guards, + } + } +} + +pub struct VariableElementsIter<'a> { + iter: std::slice::Iter<'a, Variable>, + guards: &'a Guards, +} + +impl<'a> Iterator for VariableElementsIter<'a> { + type Item = VariableNode<'a>; + + fn next(&mut self) -> Option { + self.iter + .next() + .map(|var| VariableNode::new(var, self.guards)) } } diff --git a/core/engine/src/policy/queries/scope.rs b/core/engine/src/policy/queries/scope.rs index 2d01272c..086b97b1 100644 --- a/core/engine/src/policy/queries/scope.rs +++ b/core/engine/src/policy/queries/scope.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use ahash::{HashMap, HashMapExt, HashSet, HashSetExt}; use petgraph::stable_graph::StableDiGraph; -use zen_expression::variable::VariableType; +use zen_expression::variable::{Variable, VariableType}; use crate::policy::blocks::InstanceSource; use crate::policy::ir::{DataModelIr, DictionaryIr, ParsedPolicy, Property, PropertyTypeIr}; @@ -765,7 +765,7 @@ impl VariableTypeScope for VariableType { return self.shallow_clone(); }; let mut fields: HashMap, VariableType> = obj.borrow().clone(); - fields.insert(Rc::from("$"), field_type.shallow_clone()); + fields.insert(Variable::dollar_key_rc(), field_type.shallow_clone()); VariableType::Object(Rc::new(RefCell::new(fields))) } diff --git a/core/expression/src/intellisense/dependency.rs b/core/expression/src/intellisense/dependency.rs index e6714cb3..149c3b23 100644 --- a/core/expression/src/intellisense/dependency.rs +++ b/core/expression/src/intellisense/dependency.rs @@ -1,6 +1,7 @@ use crate::functions::{ClosureFunction, FunctionKind}; use crate::lexer::{LogicalOperator, Operator}; use crate::parser::{Node, NodeMetadata}; +use crate::variable::Variable; use ahash::HashSet; use nohash_hasher::BuildNoHashHasher; use serde::{Deserialize, Serialize}; @@ -292,7 +293,7 @@ impl<'a> DependencyResolutionWalker<'a> { fn extract_path_with_spans(&self, node: &Node) -> Option<(Vec>, Vec<(u32, u32)>)> { match node { Node::Identifier(name) => Some((vec![Rc::from(*name)], vec![self.node_span(node)])), - Node::Root => Some((vec![Rc::::from("$root")], vec![self.node_span(node)])), + Node::Root => Some((vec![Variable::root_key_rc()], vec![self.node_span(node)])), Node::Member { node: n, property } => { let (mut path, mut spans) = self.extract_path_with_spans(n)?; match property { @@ -311,7 +312,7 @@ impl<'a> DependencyResolutionWalker<'a> { fn collection_source_path(node: &Node) -> Option>> { match node { Node::Identifier(name) => Some(vec![Rc::from(*name)]), - Node::Root => Some(vec![Rc::::from("$root")]), + Node::Root => Some(vec![Variable::root_key_rc()]), Node::Member { .. } => Self::extract_read_path(node), Node::Parenthesized(inner) => Self::collection_source_path(inner), Node::Binary { @@ -374,7 +375,7 @@ impl<'a> DependencyResolutionWalker<'a> { return; } Node::Identifier(name) => Rc::from(*name), - Node::Root => Rc::::from("$root"), + Node::Root => Variable::root_key_rc(), Node::Pointer => { self.resolve_pointer_chain(&chain, scope); return; @@ -487,7 +488,7 @@ impl<'a> DependencyResolutionWalker<'a> { } fn reference_dollar_chain(&mut self, chain: &FlatChain, scope: &mut Scope) { - let mut path: Vec> = vec![Rc::from("$")]; + let mut path: Vec> = vec![Variable::dollar_key_rc()]; let mut spans = vec![self.node_span(chain.root)]; let mut grouping = true; for segment in &chain.segments { @@ -673,7 +674,7 @@ impl<'a> DependencyResolutionWalker<'a> { let (alias, inner_reads, inner_refs) = match closure_node { Node::Closure { body, alias } => { let mut inner_scope = scope.clone(); - inner_scope.locals.insert(Rc::::from("$")); + inner_scope.locals.insert(Variable::dollar_key_rc()); inner_scope.pointer_collection = collection_source .as_ref() .filter(|source| !scope.is_local(source)) @@ -762,7 +763,7 @@ impl<'a> DependencyResolutionWalker<'a> { Node::Closure { body, alias: _ } => { let mut inner = scope.clone(); - inner.locals.insert(Rc::::from("$")); + inner.locals.insert(Variable::dollar_key_rc()); inner.pointer_collection = None; self.resolve(body, &mut inner); } @@ -836,7 +837,7 @@ impl<'a> DependencyResolutionWalker<'a> { } Node::Root => { - let path: Vec> = vec![Rc::::from("$root")]; + let path: Vec> = vec![Variable::root_key_rc()]; let span = self.node_span(node); self.reads.push(ReadDependency::Direct { path: path.clone(), @@ -865,7 +866,7 @@ impl<'a> DependencyResolutionWalker<'a> { fn extract_read_path(node: &Node) -> Option>> { match node { Node::Identifier(name) => Some(vec![Rc::from(*name)]), - Node::Root => Some(vec![Rc::::from("$root")]), + Node::Root => Some(vec![Variable::root_key_rc()]), Node::Member { node, property } => { let mut path = Self::extract_read_path(node)?; match property { diff --git a/core/expression/src/scope.rs b/core/expression/src/scope.rs index 6f9f8a92..082eb683 100644 --- a/core/expression/src/scope.rs +++ b/core/expression/src/scope.rs @@ -44,10 +44,6 @@ impl Scope { } pub fn set_local(&mut self, name: Symbol, value: Variable) { - if !matches!(self.base, Variable::Object(_)) { - self.base = Variable::empty_object(); - } - match self .locals .iter_mut() @@ -100,12 +96,7 @@ impl Scope { } let Variable::Object(base) = &self.base else { - return Variable::from_object( - self.locals - .iter() - .map(|(key, value)| (key.clone(), value.clone())) - .collect(), - ); + return self.base.shallow_clone(); }; let mut map = base.borrow().clone(); diff --git a/core/expression/src/variable/mod.rs b/core/expression/src/variable/mod.rs index ef41a48d..b9e2a4cf 100644 --- a/core/expression/src/variable/mod.rs +++ b/core/expression/src/variable/mod.rs @@ -1,4 +1,5 @@ pub use zen_types::rcvalue::*; +pub use zen_types::symbol::Symbol; pub use zen_types::variable::*; pub use zen_types::variable_type::*; diff --git a/core/macros/src/to_variable.rs b/core/macros/src/to_variable.rs index 693ee4f7..6340eeaf 100644 --- a/core/macros/src/to_variable.rs +++ b/core/macros/src/to_variable.rs @@ -47,9 +47,7 @@ pub fn to_variable_impl(input: TokenStream) -> TokenStream { const _: () = { extern crate zen_expression as _zen_expression; - use _zen_expression::variable::{Variable as _Variable, VariableMap as _VariableMap, VariableMapExt, ToVariable as _ToVariable}; - use ::std::rc::Rc as _Rc; - use ::zen_types::symbol::Symbol as _Symbol; + use _zen_expression::variable::{Variable as _Variable, VariableMap as _VariableMap, VariableMapExt, ToVariable as _ToVariable, Symbol as _Symbol}; #impl_block }; @@ -225,7 +223,7 @@ fn generate_variant_arm( serde_derive_internals::ast::Style::Unit => { quote! { #enum_ident::#variant_ident => { - _Variable::String(_Rc::from(#variant_name)) + _Variable::String(_Symbol::from_static(#variant_name)) } } } @@ -234,7 +232,7 @@ fn generate_variant_arm( quote! { #enum_ident::#variant_ident(value) => { let mut map = _VariableMap::with_capacity(2); - map.insert(_Symbol::from(#type_key), _Variable::String(_Rc::from(#variant_name))); + map.insert(_Symbol::from(#type_key), _Variable::String(_Symbol::from_static(#variant_name))); map.insert(_Symbol::from(#value_key), value.to_variable()); _Variable::from_object(map) } @@ -251,7 +249,7 @@ fn generate_variant_arm( quote! { #enum_ident::#variant_ident(#(#field_patterns),*) => { let mut map = _VariableMap::with_capacity(2); - map.insert(_Symbol::from(#type_key), _Variable::String(_Rc::from(#variant_name))); + map.insert(_Symbol::from(#type_key), _Variable::String(_Symbol::from_static(#variant_name))); map.insert(_Symbol::from(#value_key), (#(#field_patterns)*).to_variable()); _Variable::from_object(map) } @@ -267,7 +265,7 @@ fn generate_variant_arm( quote! { #enum_ident::#variant_ident(#(#field_patterns),*) => { let mut map = _VariableMap::with_capacity(#field_count + 1); - map.insert(_Symbol::from(#type_key), _Variable::String(_Rc::from(#variant_name))); + map.insert(_Symbol::from(#type_key), _Variable::String(_Symbol::from_static(#variant_name))); #(#field_mappings)* _Variable::from_object(map) } @@ -304,7 +302,7 @@ fn generate_variant_arm( quote! { #enum_ident::#variant_ident { #(#field_patterns),* } => { let mut map = _VariableMap::with_capacity(#field_count); - map.insert(_Symbol::from(#type_key), _Variable::String(_Rc::from(#variant_name))); + map.insert(_Symbol::from(#type_key), _Variable::String(_Symbol::from_static(#variant_name))); #(#field_mappings)* _Variable::from_object(map) } diff --git a/core/types/src/lib.rs b/core/types/src/lib.rs index 4d6e2a96..4dcb05a2 100644 --- a/core/types/src/lib.rs +++ b/core/types/src/lib.rs @@ -1,4 +1,4 @@ -#![deny(unsafe_code)] +#![forbid(unsafe_code)] mod constant; pub mod decision; diff --git a/core/types/src/rccell.rs b/core/types/src/rccell.rs index a598bdc1..b04883d3 100644 --- a/core/types/src/rccell.rs +++ b/core/types/src/rccell.rs @@ -1,314 +1,4 @@ -#![allow(unsafe_code)] +use std::cell::RefCell; +use std::rc::Rc; -use std::cell::{Cell, RefCell, UnsafeCell}; -use std::fmt::{Debug, Formatter}; -use std::ops::{Deref, DerefMut}; -use std::ptr::NonNull; - -const POOL_CAP: usize = 512; -const RETAINED_CAPACITY: usize = 64; - -pub struct PoolStore { - slots: Vec>, - free: unsafe fn(NonNull), -} - -impl PoolStore { - pub fn new(free: unsafe fn(NonNull)) -> Self { - Self { - slots: Vec::new(), - free, - } - } -} - -impl Drop for PoolStore { - fn drop(&mut self) { - for slot in self.slots.drain(..) { - unsafe { (self.free)(slot) } - } - } -} - -pub trait Recycle: Sized + Default + 'static { - fn with_pool(f: impl FnOnce(&mut PoolStore) -> R) -> Option; - - fn recycle(&mut self); -} - -struct Inner { - strong: Cell, - borrow: Cell, - value: UnsafeCell, -} - -unsafe fn free_slot(ptr: NonNull) { - unsafe { drop(Box::from_raw(ptr.cast::>().as_ptr())) } -} - -pub struct RcCell { - ptr: NonNull>, -} - -impl RcCell { - pub fn new(value: T) -> Self { - match Self::take_slot() { - Some(ptr) => { - unsafe { *ptr.as_ref().value.get() = value } - Self { ptr } - } - None => Self::boxed(value), - } - } - - pub fn recycled() -> Self { - match Self::take_slot() { - Some(ptr) => Self { ptr }, - None => Self::boxed(T::default()), - } - } - - fn boxed(value: T) -> Self { - let inner = Box::new(Inner { - strong: Cell::new(1), - borrow: Cell::new(0), - value: UnsafeCell::new(value), - }); - - Self { - ptr: unsafe { NonNull::new_unchecked(Box::into_raw(inner)) }, - } - } - - fn take_slot() -> Option>> { - let slot = T::with_pool(|pool| pool.slots.pop())??; - let ptr = slot.cast::>(); - unsafe { - ptr.as_ref().strong.set(1); - ptr.as_ref().borrow.set(0); - } - - Some(ptr) - } - - #[inline] - fn inner(&self) -> &Inner { - unsafe { self.ptr.as_ref() } - } - - #[inline] - pub fn borrow(&self) -> Ref<'_, T> { - let inner = self.inner(); - let state = inner.borrow.get(); - if state < 0 { - panic!("RcCell is already mutably borrowed"); - } - - inner.borrow.set(state + 1); - Ref { inner } - } - - #[inline] - pub fn borrow_mut(&self) -> RefMut<'_, T> { - let inner = self.inner(); - if inner.borrow.get() != 0 { - panic!("RcCell is already borrowed"); - } - - inner.borrow.set(-1); - RefMut { inner } - } - - pub fn try_borrow(&self) -> Option> { - let inner = self.inner(); - let state = inner.borrow.get(); - if state < 0 { - return None; - } - - inner.borrow.set(state + 1); - Some(Ref { inner }) - } - - pub fn try_borrow_mut(&self) -> Option> { - let inner = self.inner(); - if inner.borrow.get() != 0 { - return None; - } - - inner.borrow.set(-1); - Some(RefMut { inner }) - } - - /// Reads through the cell without tracking a borrow. The caller guarantees - /// no `borrow_mut` overlaps the returned reference, and that the `RcCell` - /// outlives it. - pub unsafe fn get_ref<'b>(&self) -> &'b T { - unsafe { &*self.inner().value.get() } - } - - #[inline] - pub fn ptr_eq(a: &Self, b: &Self) -> bool { - a.ptr == b.ptr - } - - #[inline] - pub fn as_ptr(this: &Self) -> *const T { - this.inner().value.get() - } - - pub fn strong_count(this: &Self) -> usize { - this.inner().strong.get() - } - - pub fn try_unwrap(this: Self) -> Result { - if this.inner().strong.get() != 1 { - return Err(this); - } - - Ok(unsafe { std::mem::take(&mut *this.inner().value.get()) }) - } -} - -impl Clone for RcCell { - #[inline] - fn clone(&self) -> Self { - let inner = self.inner(); - inner.strong.set(inner.strong.get() + 1); - - Self { ptr: self.ptr } - } -} - -impl Drop for RcCell { - fn drop(&mut self) { - let inner = self.inner(); - let strong = inner.strong.get() - 1; - inner.strong.set(strong); - if strong != 0 { - return; - } - - // Runs before the pool is touched: recycling drops nested cells, which - // re-enter the pool for their own type. - unsafe { (*inner.value.get()).recycle() } - inner.borrow.set(0); - - let ptr = self.ptr; - let pooled = T::with_pool(|pool| { - if pool.slots.len() >= POOL_CAP { - return false; - } - - pool.slots.push(ptr.cast::()); - true - }); - - if pooled != Some(true) { - unsafe { drop(Box::from_raw(ptr.as_ptr())) } - } - } -} - -impl Default for RcCell { - fn default() -> Self { - Self::recycled() - } -} - -impl PartialEq for RcCell { - fn eq(&self, other: &Self) -> bool { - Self::ptr_eq(self, other) || *self.borrow() == *other.borrow() - } -} - -impl Eq for RcCell {} - -impl serde::Serialize for RcCell { - fn serialize(&self, serializer: S) -> Result { - self.borrow().serialize(serializer) - } -} - -impl Debug for RcCell { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - Debug::fmt(&*self.borrow(), f) - } -} - -pub struct Ref<'a, T: Recycle> { - inner: &'a Inner, -} - -impl<'a, T: Recycle> Deref for Ref<'a, T> { - type Target = T; - - #[inline] - fn deref(&self) -> &T { - unsafe { &*self.inner.value.get() } - } -} - -impl<'a, T: Recycle> Drop for Ref<'a, T> { - #[inline] - fn drop(&mut self) { - self.inner.borrow.set(self.inner.borrow.get() - 1); - } -} - -pub struct RefMut<'a, T: Recycle> { - inner: &'a Inner, -} - -impl<'a, T: Recycle> Deref for RefMut<'a, T> { - type Target = T; - - #[inline] - fn deref(&self) -> &T { - unsafe { &*self.inner.value.get() } - } -} - -impl<'a, T: Recycle> DerefMut for RefMut<'a, T> { - #[inline] - fn deref_mut(&mut self) -> &mut T { - unsafe { &mut *self.inner.value.get() } - } -} - -impl<'a, T: Recycle> Drop for RefMut<'a, T> { - #[inline] - fn drop(&mut self) { - self.inner.borrow.set(0); - } -} - -macro_rules! recyclable { - ($t:ty, $tls:ident, $reset:expr) => { - thread_local! { - static $tls: RefCell = RefCell::new(PoolStore::new(free_slot::<$t>)); - } - - impl Recycle for $t { - #[inline] - fn with_pool(f: impl FnOnce(&mut PoolStore) -> R) -> Option { - $tls.try_with(|pool| f(&mut pool.borrow_mut())).ok() - } - - #[inline] - fn recycle(&mut self) { - let reset: fn(&mut $t) = $reset; - reset(self) - } - } - }; -} - -recyclable!(Vec, ARRAY_POOL, |vec| { - vec.clear(); - if vec.capacity() > RETAINED_CAPACITY { - *vec = Vec::new(); - } -}); - -recyclable!(crate::variable::VariableMap, OBJECT_POOL, |map| map.reset()); +pub type RcCell = Rc>; diff --git a/core/types/src/variable/conv.rs b/core/types/src/variable/conv.rs index caf872ee..0ee276b1 100644 --- a/core/types/src/variable/conv.rs +++ b/core/types/src/variable/conv.rs @@ -119,18 +119,22 @@ impl From for Value { } Variable::String(s) => Value::String(s.to_string()), Variable::Array(arr) => { - let vec = RcCell::try_unwrap(arr).unwrap_or_else(|s| { - let borrowed = s.borrow(); - borrowed.clone() - }); + let vec = RcCell::try_unwrap(arr) + .map(|cell| cell.into_inner()) + .unwrap_or_else(|s| { + let borrowed = s.borrow(); + borrowed.clone() + }); Value::Array(vec.into_iter().map(Value::from).collect()) } Variable::Object(obj) => { - let hmap = RcCell::try_unwrap(obj).unwrap_or_else(|s| { - let borrowed = s.borrow(); - borrowed.clone() - }); + let hmap = RcCell::try_unwrap(obj) + .map(|cell| cell.into_inner()) + .unwrap_or_else(|s| { + let borrowed = s.borrow(); + borrowed.clone() + }); Value::Object( hmap.into_iter() diff --git a/core/types/src/variable/map.rs b/core/types/src/variable/map.rs index 47596432..cce12000 100644 --- a/core/types/src/variable/map.rs +++ b/core/types/src/variable/map.rs @@ -49,18 +49,6 @@ impl VariableMap { } } - pub fn reset(&mut self) { - match &mut self.0 { - Repr::Small(entries) => { - entries.clear(); - if entries.spilled() { - *entries = Entries::new(); - } - } - Repr::Large(_) => self.0 = Repr::Small(Entries::new()), - } - } - #[inline] pub fn get(&self, key: &Symbol) -> Option<&Variable> { match &self.0 { diff --git a/core/types/src/variable/mod.rs b/core/types/src/variable/mod.rs index 5839a52d..e897fac7 100644 --- a/core/types/src/variable/mod.rs +++ b/core/types/src/variable/mod.rs @@ -5,6 +5,7 @@ use rust_decimal::Decimal; use rust_decimal::prelude::Zero; use serde_json::Value; use std::any::Any; +use std::cell::RefCell; use std::fmt::{Debug, Display, Formatter}; use std::ops::Deref; use std::rc::Rc; @@ -27,6 +28,11 @@ pub use crate::rccell::RcCell; pub use crate::variable::map::{Iter as MapIter, VariableMap}; +thread_local! { + static DOLLAR_KEY_RC: Rc = Rc::from("$"); + static ROOT_KEY_RC: Rc = Rc::from("$root"); +} + pub enum Variable { Null, Bool(bool), @@ -62,8 +68,16 @@ impl Variable { Symbol::from(name) } + pub fn dollar_key_rc() -> Rc { + DOLLAR_KEY_RC.with(Rc::clone) + } + + pub fn root_key_rc() -> Rc { + ROOT_KEY_RC.with(Rc::clone) + } + pub fn from_array(arr: Vec) -> Self { - Self::Array(RcCell::new(arr)) + Self::Array(Rc::new(RefCell::new(arr))) } pub fn serialize_ref(&self) -> RcValue { @@ -75,7 +89,7 @@ impl Variable { } pub fn from_object(obj: VariableMap) -> Self { - Self::Object(RcCell::new(obj)) + Self::Object(Rc::new(RefCell::new(obj))) } pub fn empty_object() -> Self { @@ -413,7 +427,7 @@ fn merge_variables( // Only update doc if changes were made if changed { if let Some(new_map) = new_map { - *doc = Variable::Object(RcCell::new(new_map)); + *doc = Variable::Object(Rc::new(RefCell::new(new_map))); } return true; } diff --git a/core/types/src/variable/ref_deser.rs b/core/types/src/variable/ref_deser.rs index 92e0e0c2..4ce2e386 100644 --- a/core/types/src/variable/ref_deser.rs +++ b/core/types/src/variable/ref_deser.rs @@ -1,4 +1,3 @@ -use crate::rccell::RcCell; use crate::rcvalue::RcValue; use crate::variable::Variable; use crate::variable::VariableMap; @@ -27,10 +26,10 @@ impl RefDeserializer { for (i, _) in refs_array.iter().enumerate() { match &refs_array[i] { RcValue::Array(_) => { - self.refs[i] = Some(Variable::Array(RcCell::new(Vec::new()))); + self.refs[i] = Some(Variable::from_array(Vec::new())); } RcValue::Object(_) => { - self.refs[i] = Some(Variable::Object(RcCell::new(VariableMap::default()))); + self.refs[i] = Some(Variable::from_object(VariableMap::default())); } _ => { self.refs[i] = Some(self.deserialize_value(&refs_array[i])?); @@ -113,7 +112,7 @@ impl RefDeserializer { for item in arr { items.push(self.deserialize_value(item)?); } - Ok(Variable::Array(RcCell::new(items))) + Ok(Variable::from_array(items)) } RcValue::Object(obj) => { let mut map = VariableMap::with_capacity(obj.len()); @@ -122,7 +121,7 @@ impl RefDeserializer { let value_var = self.deserialize_value(value)?; map.insert(key_var, value_var); } - Ok(Variable::Object(RcCell::new(map))) + Ok(Variable::from_object(map)) } } } diff --git a/core/types/src/variable/ref_ser.rs b/core/types/src/variable/ref_ser.rs index 507f02b9..bc2a3a5e 100644 --- a/core/types/src/variable/ref_ser.rs +++ b/core/types/src/variable/ref_ser.rs @@ -54,7 +54,7 @@ impl RefSerializer { result.insert(Rc::from("$refs"), RcValue::Array(self.ref_data)); } - result.insert(Rc::from("$root"), data); + result.insert(Variable::root_key_rc(), data); RcValue::Object(result) } diff --git a/core/types/tests/ref_ser.rs b/core/types/tests/ref_ser.rs index e44ac463..3abfc424 100644 --- a/core/types/tests/ref_ser.rs +++ b/core/types/tests/ref_ser.rs @@ -1,8 +1,8 @@ use rust_decimal_macros::dec; use serde_json::json; +use std::cell::RefCell; use std::error::Error; use std::rc::Rc; -use zen_types::rccell::RcCell; use zen_types::rcvalue::RcValue; use zen_types::variable::Variable; use zen_types::variable::VariableMap as HashMap; @@ -145,7 +145,7 @@ fn no_refs_when_below_threshold() -> TestResult { #[test] fn serialize_circular_references() -> TestResult { // Create a shared object that will be referenced multiple times - let shared_obj = RcCell::new({ + let shared_obj = Rc::new(RefCell::new({ let mut map = HashMap::new(); map.insert( zen_types::symbol::Symbol::from("shared_data"), @@ -156,7 +156,7 @@ fn serialize_circular_references() -> TestResult { Variable::Number(dec!(42.0)), ); map - }); + })); // Create a structure where the same object appears in multiple places let mut root_map = HashMap::new(); @@ -173,7 +173,7 @@ fn serialize_circular_references() -> TestResult { Variable::Object(shared_obj), ); - let var = Variable::Object(RcCell::new(root_map)); + let var = Variable::Object(Rc::new(RefCell::new(root_map))); let serialized = var.serialize_ref(); let deserialized = Variable::deserialize_ref(serialized)?; @@ -186,11 +186,11 @@ fn serialize_circular_references() -> TestResult { #[test] fn serialize_same_array_multiple_locations() -> TestResult { // Create a shared array - let shared_array = RcCell::new(vec![ + let shared_array = Rc::new(RefCell::new(vec![ Variable::String(("item_one").into()), Variable::String(("item_two").into()), Variable::Number(dec!(123.0)), - ]); + ])); let var = Variable::from(json!({ "list1": shared_array.clone(), @@ -223,7 +223,7 @@ fn serialize_mixed_shared_references() -> TestResult { Variable::String(shared_string.as_ref().into()), ); - let shared_obj = RcCell::new(obj_map); + let shared_obj = Rc::new(RefCell::new(obj_map)); // Use the shared object in multiple places let var = Variable::from(json!({ @@ -247,11 +247,11 @@ fn serialize_shared_array_with_shared_strings() -> TestResult { let shared_string: Rc = Rc::from("shared_string_value"); // Create a shared array containing the shared string - let shared_array = RcCell::new(vec![ + let shared_array = Rc::new(RefCell::new(vec![ Variable::String(shared_string.as_ref().into()), Variable::Number(dec!(42.0)), Variable::String(shared_string.as_ref().into()), - ]); + ])); // Use the shared array in multiple places let mut root_map = HashMap::new(); @@ -268,7 +268,7 @@ fn serialize_shared_array_with_shared_strings() -> TestResult { Variable::Array(shared_array), ); - let var = Variable::Object(RcCell::new(root_map)); + let var = Variable::Object(Rc::new(RefCell::new(root_map))); let serialized = var.serialize_ref(); let deserialized = Variable::deserialize_ref(serialized)?;