Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-05-24 - Context Map Lookups and Locking Contention
**Learning:** Double lookups inside `MutexGuard` sections combined with unnecessary entire Enum cloning create notable lock contention and memory allocation overhead. Specifically, `binding.get(name).is_none()` followed by `binding.get(name).unwrap()` traverses the map twice unnecessarily.
**Action:** Always perform a single `.get(name)` map lookup, and match on the borrowed enum reference to clone ONLY the internal value (like an `Arc` or `Value`), bypassing full `ContextValue` copying. Drop the `MutexGuard` explicitly BEFORE invoking external closures/functions (like `func(Vec::new())`) to reduce contention and prevent deadlocks on reentrant code.
2 changes: 1 addition & 1 deletion benches/display_expression.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use criterion::{criterion_group, criterion_main, Criterion, black_box};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use expression_engine::{parse_expression, ExprAST};

fn bench_display_expression(c: &mut Criterion) {
Expand Down
59 changes: 48 additions & 11 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,17 @@ impl Context {
}

pub fn get_func(&self, name: &str) -> Option<Arc<InnerFunction>> {
let value = self.get(name)?;
let binding = self.0.lock().unwrap();
let value = binding.get(name)?;
match value {
ContextValue::Function(func) => Some(func.clone()),
ContextValue::Variable(_) => None,
}
Comment on lines +34 to 39
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For improved readability and to make the code more idiomatic, you could consider using if let to handle the Some case directly. This avoids the need for ? followed by a match and makes the intent clearer.

Suggested change
let binding = self.0.lock().unwrap();
let value = binding.get(name)?;
match value {
ContextValue::Function(func) => Some(func.clone()),
ContextValue::Variable(_) => None,
}
let binding = self.0.lock().unwrap();
if let Some(ContextValue::Function(func)) = binding.get(name) {
Some(func.clone())
} else {
None
}

}

pub fn get_variable(&self, name: &str) -> Option<Value> {
let value = self.get(name)?;
let binding = self.0.lock().unwrap();
let value = binding.get(name)?;
match value {
ContextValue::Variable(v) => Some(v.clone()),
ContextValue::Function(_) => None,
Expand All @@ -53,15 +55,15 @@ impl Context {
}

pub fn value(&self, name: &str) -> Result<Value> {
let binding = self.0.lock().unwrap();
if binding.get(name).is_none() {
return Ok(Value::None);
}
let value = binding.get(name).unwrap();
match value {
ContextValue::Variable(v) => Ok(v.clone()),
ContextValue::Function(func) => func(Vec::new()),
}
let func = {
let binding = self.0.lock().unwrap();
match binding.get(name) {
Some(ContextValue::Variable(v)) => return Ok(v.clone()),
Some(ContextValue::Function(func)) => func.clone(),
None => return Ok(Value::None),
}
};
func(Vec::new())
}
}

Expand Down Expand Up @@ -104,3 +106,38 @@ macro_rules! create_context {
ctx
}};
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_context_lookups() {
let mut ctx = Context::new();
ctx.set_variable("my_var", Value::from(42));
ctx.set_func("my_func", Arc::new(|_| Ok(Value::from(100))));

// get_variable
assert_eq!(ctx.get_variable("my_var"), Some(Value::from(42)));
assert_eq!(ctx.get_variable("my_func"), None);
assert_eq!(ctx.get_variable("non_existent"), None);

// get_func
assert!(ctx.get_func("my_func").is_some());
assert!(ctx.get_func("my_var").is_none());
assert!(ctx.get_func("non_existent").is_none());

// value
assert_eq!(ctx.value("my_var").unwrap(), Value::from(42));
assert_eq!(ctx.value("my_func").unwrap(), Value::from(100));
assert_eq!(ctx.value("non_existent").unwrap(), Value::None);

// get
assert!(matches!(ctx.get("my_var"), Some(ContextValue::Variable(_))));
assert!(matches!(
ctx.get("my_func"),
Some(ContextValue::Function(_))
));
assert!(matches!(ctx.get("non_existent"), None));
}
}
14 changes: 4 additions & 10 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,16 @@ impl<'a> fmt::Display for ExprAST<'a> {
Self::Unary(op, rhs) => {
write!(f, "Unary AST: Op: {}, Rhs: {}", op, rhs)
}
Self::Binary(op, lhs, rhs) => write!(
f,
"Binary AST: Op: {}, Lhs: {}, Rhs: {}",
op,
lhs,
rhs
),
Self::Binary(op, lhs, rhs) => {
write!(f, "Binary AST: Op: {}, Lhs: {}, Rhs: {}", op, lhs, rhs)
}
Self::Postfix(lhs, op) => {
write!(f, "Postfix AST: Lhs: {}, Op: {}", lhs, op,)
}
Self::Ternary(condition, lhs, rhs) => write!(
f,
"Ternary AST: Condition: {}, Lhs: {}, Rhs: {}",
condition,
lhs,
rhs
condition, lhs, rhs
),
Self::Reference(name) => write!(f, "Reference AST: reference: {}", name),
Self::Function(name, params) => {
Expand Down
Loading