remove unsafe usages, further improve perf

This commit is contained in:
Stefan
2026-08-03 14:52:03 +02:00
parent f31f952256
commit 6bf8c81768
21 changed files with 267 additions and 452 deletions
Generated
+14
View File
@@ -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",
+2
View File
@@ -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"] }
+1 -1
View File
@@ -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)]
+18 -8
View File
@@ -54,15 +54,25 @@ impl DecisionLoader for CachedLoader {
key: &'a str,
) -> Pin<Box<dyn Future<Output = LoaderResponse> + '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)
})
+16 -1
View File
@@ -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)?;
+1 -1
View File
@@ -21,7 +21,7 @@ impl NodeHandler for CustomNodeHandler {
async fn handle(&self, ctx: NodeContext<Self::NodeData, Self::TraceData>) -> 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(),
+1 -1
View File
@@ -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);
+147 -59
View File
@@ -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<Variable>>;
self_cell!(
struct MapGuard {
owner: RcCell<VariableMap>,
#[covariant]
dependent: MapRef,
}
);
self_cell!(
struct VecGuard {
owner: RcCell<Vec<Variable>>,
#[covariant]
dependent: VecRef,
}
);
pub struct Guards {
objects: Arena<MapGuard>,
arrays: Arena<VecGuard>,
}
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<str>;
type StringBuffer = Variable;
@@ -25,10 +71,11 @@ impl Json for VariableJson {
fn with_string_node<T>(
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<u64> {
self.0.to_u64()
self.0.is_integer().then(|| self.0.to_u64()).flatten()
}
fn as_i64(&self) -> Option<i64> {
self.0.to_i64()
self.0.is_integer().then(|| self.0.to_i64()).flatten()
}
fn as_f64(&self) -> Option<f64> {
@@ -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<T>,
) -> &'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<ObjectNode<'a>> {
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<ArrayNode<'a>> {
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<Cow<'a, str>> {
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<VariableNumber> {
match self {
match self.var {
Variable::Number(number) => Some(VariableNumber(*number)),
_ => None,
}
}
fn as_boolean(&self) -> Option<bool> {
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<NodeIdentity> {
Some(NodeIdentity::new(
std::ptr::from_ref::<Variable>(*self) as usize
std::ptr::from_ref::<Variable>(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<str>) -> Option<&'a Variable> {
VariableMap::get_str(self, key)
fn get(&self, key: &Box<str>) -> Option<VariableNode<'a>> {
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::Item> {
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::Item> {
self.iter
.next()
.map(|var| VariableNode::new(var, self.guards))
}
}
+2 -2
View File
@@ -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<Rc<str>, 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)))
}
@@ -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<Rc<str>>, Vec<(u32, u32)>)> {
match node {
Node::Identifier(name) => Some((vec![Rc::from(*name)], vec![self.node_span(node)])),
Node::Root => Some((vec![Rc::<str>::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<Vec<Rc<str>>> {
match node {
Node::Identifier(name) => Some(vec![Rc::from(*name)]),
Node::Root => Some(vec![Rc::<str>::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::<str>::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<Rc<str>> = vec![Rc::from("$")];
let mut path: Vec<Rc<str>> = 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::<str>::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::<str>::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<Rc<str>> = vec![Rc::<str>::from("$root")];
let path: Vec<Rc<str>> = 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<Vec<Rc<str>>> {
match node {
Node::Identifier(name) => Some(vec![Rc::from(*name)]),
Node::Root => Some(vec![Rc::<str>::from("$root")]),
Node::Root => Some(vec![Variable::root_key_rc()]),
Node::Member { node, property } => {
let mut path = Self::extract_read_path(node)?;
match property {
+1 -10
View File
@@ -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();
+1
View File
@@ -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::*;
+6 -8
View File
@@ -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)
}
+1 -1
View File
@@ -1,4 +1,4 @@
#![deny(unsafe_code)]
#![forbid(unsafe_code)]
mod constant;
pub mod decision;
+3 -313
View File
@@ -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<NonNull<u8>>,
free: unsafe fn(NonNull<u8>),
}
impl PoolStore {
pub fn new(free: unsafe fn(NonNull<u8>)) -> 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<R>(f: impl FnOnce(&mut PoolStore) -> R) -> Option<R>;
fn recycle(&mut self);
}
struct Inner<T> {
strong: Cell<usize>,
borrow: Cell<isize>,
value: UnsafeCell<T>,
}
unsafe fn free_slot<T>(ptr: NonNull<u8>) {
unsafe { drop(Box::from_raw(ptr.cast::<Inner<T>>().as_ptr())) }
}
pub struct RcCell<T: Recycle> {
ptr: NonNull<Inner<T>>,
}
impl<T: Recycle> RcCell<T> {
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<NonNull<Inner<T>>> {
let slot = T::with_pool(|pool| pool.slots.pop())??;
let ptr = slot.cast::<Inner<T>>();
unsafe {
ptr.as_ref().strong.set(1);
ptr.as_ref().borrow.set(0);
}
Some(ptr)
}
#[inline]
fn inner(&self) -> &Inner<T> {
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<Ref<'_, T>> {
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<RefMut<'_, T>> {
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<T, Self> {
if this.inner().strong.get() != 1 {
return Err(this);
}
Ok(unsafe { std::mem::take(&mut *this.inner().value.get()) })
}
}
impl<T: Recycle> Clone for RcCell<T> {
#[inline]
fn clone(&self) -> Self {
let inner = self.inner();
inner.strong.set(inner.strong.get() + 1);
Self { ptr: self.ptr }
}
}
impl<T: Recycle> Drop for RcCell<T> {
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::<u8>());
true
});
if pooled != Some(true) {
unsafe { drop(Box::from_raw(ptr.as_ptr())) }
}
}
}
impl<T: Recycle> Default for RcCell<T> {
fn default() -> Self {
Self::recycled()
}
}
impl<T: Recycle + PartialEq> PartialEq for RcCell<T> {
fn eq(&self, other: &Self) -> bool {
Self::ptr_eq(self, other) || *self.borrow() == *other.borrow()
}
}
impl<T: Recycle + Eq> Eq for RcCell<T> {}
impl<T: Recycle + serde::Serialize> serde::Serialize for RcCell<T> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.borrow().serialize(serializer)
}
}
impl<T: Recycle + Debug> Debug for RcCell<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&*self.borrow(), f)
}
}
pub struct Ref<'a, T: Recycle> {
inner: &'a Inner<T>,
}
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<T>,
}
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<PoolStore> = RefCell::new(PoolStore::new(free_slot::<$t>));
}
impl Recycle for $t {
#[inline]
fn with_pool<R>(f: impl FnOnce(&mut PoolStore) -> R) -> Option<R> {
$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<crate::variable::Variable>, 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<T> = Rc<RefCell<T>>;
+12 -8
View File
@@ -119,18 +119,22 @@ impl From<Variable> 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()
-12
View File
@@ -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 {
+17 -3
View File
@@ -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<str> = Rc::from("$");
static ROOT_KEY_RC: Rc<str> = Rc::from("$root");
}
pub enum Variable {
Null,
Bool(bool),
@@ -62,8 +68,16 @@ impl Variable {
Symbol::from(name)
}
pub fn dollar_key_rc() -> Rc<str> {
DOLLAR_KEY_RC.with(Rc::clone)
}
pub fn root_key_rc() -> Rc<str> {
ROOT_KEY_RC.with(Rc::clone)
}
pub fn from_array(arr: Vec<Self>) -> 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;
}
+4 -5
View File
@@ -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))
}
}
}
+1 -1
View File
@@ -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)
}
+10 -10
View File
@@ -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<str> = 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)?;