From 6fd29a9b5e100abd46262ef841012a05d83fffed Mon Sep 17 00:00:00 2001 From: Amit Saxena Date: Sat, 4 Apr 2026 17:03:22 +0530 Subject: [PATCH] wasm bindings updates --- CHANGELOG.md | 4 ++ Cargo.toml | 2 +- bindings/wasm/Cargo.toml | 1 + bindings/wasm/src/lib.rs | 105 +++++++++++++++++--------------------- sdk/js/actra/package.json | 2 +- 5 files changed, 55 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11b89a4..9824d3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### v0.7.8 - 04 Apr 2026 +- WASM : updated bindings to support RwLock & slab for O(1) allocation and removed mutex +- WASM : hard coded max instance size to 2000 for security + ### v0.7.7 - 28 March 2026 - browser load js sdk bug fix diff --git a/Cargo.toml b/Cargo.toml index 839a782..31232b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ members = [ resolver = "2" [workspace.package] -version = "0.7.7" +version = "0.7.8" edition = "2021" license = "Apache-2.0" authors = ["Amit Saxena"] diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index c0ea578..c0d2805 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -16,6 +16,7 @@ console_error_panic_hook = "0.1" serde_yaml = "0.9" serde = "1.0.228" serde_json = "1.0.149" +slab = "0.4.12" [package.metadata.wasm-pack.profile.release] wasm-opt = false diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index a3737d5..b92f866 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -12,11 +12,11 @@ //! All validation and evaluation logic resides in `actra-core`. use serde::{Deserialize, Serialize}; -use std::sync::{Mutex, OnceLock}; +use std::sync::{RwLock, OnceLock}; use std::sync::Arc; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::cell::RefCell; - +use slab::Slab; use actra::ast::PolicyAst; use actra::compiler::{compile_policy, compile_with_governance}; @@ -26,8 +26,7 @@ use actra::ir::{CompiledPolicy, Effect}; use actra::schema::{Schema, SchemaAst}; use actra::compiler_version as core_compiler_version; - - +const MAX_INSTANCES: usize = 2000; fn safe_exec(f: F) -> u64 where @@ -52,10 +51,10 @@ pub extern "C" fn actra_free(instance_id: i32) { return; } - let mut instances = get_instances().lock().unwrap(); + let mut instances = get_instances().write().unwrap(); - if let Some(slot) = instances.get_mut(instance_id as usize) { - *slot = None; + if instances.contains(instance_id as usize) { + instances.remove(instance_id as usize); } })); } @@ -148,11 +147,10 @@ struct ActraInstance { //Allows freeing slots without shifting indices //Prevents ID corruption //Keeps instance IDs stable -// TODO: Replace Vec> with Slab for O(1) allocation and cleaner semantics -static INSTANCES: OnceLock>>> = OnceLock::new(); +static INSTANCES: OnceLock>> = OnceLock::new(); -fn get_instances() -> &'static Mutex>> { - INSTANCES.get_or_init(|| Mutex::new(Vec::new())) +fn get_instances() -> &'static RwLock> { + INSTANCES.get_or_init(|| RwLock::new(Slab::new())) } #[no_mangle] @@ -178,21 +176,18 @@ pub extern "C" fn actra_create( governance_yaml, )?; - let mut instances = get_instances().lock().unwrap(); + let mut instances = get_instances().write().unwrap(); + + if instances.len() >= MAX_INSTANCES { + return Err("instance limit reached".to_string()); + } - //Slot reuse let instance = ActraInstance { compiled_policy: Arc::new(compiled_policy), }; - - if let Some((idx, slot)) = instances.iter_mut().enumerate().find(|(_, s)| s.is_none()) { - *slot = Some(instance); - Ok(idx.to_string()) - } else { - instances.push(Some(instance)); - Ok((instances.len() - 1).to_string()) - } + let id = instances.insert(instance); + Ok(id.to_string()) }) } @@ -201,39 +196,37 @@ pub extern "C" fn actra_evaluate( instance_id: i32, input_buf: u64, ) -> u64 { -safe_exec(|| { + safe_exec(|| { - if instance_id < 0 { - return Err("invalid instance_id".to_string()); - } + if instance_id < 0 { + return Err("invalid instance_id".to_string()); + } - let input_str = read_buffer(input_buf, "input")?; + let input_str = read_buffer(input_buf, "input")?; - let input: JsEvaluationInput = - serde_json::from_str(input_str).map_err(|e| e.to_string())?; + let input: JsEvaluationInput = + serde_json::from_str(input_str).map_err(|e| e.to_string())?; - let compiled_policy = { - let instances = get_instances().lock().unwrap(); + let instances = get_instances().read().unwrap(); - match instances.get(instance_id as usize) { - Some(Some(i)) => Arc::clone(&i.compiled_policy), - _ => return Err("invalid instance_id".to_string()), - } - }; + let compiled_policy = match instances.get(instance_id as usize) { + Some(i) => Arc::clone(&i.compiled_policy), + None => return Err("invalid instance_id".to_string()), + }; - let eval_input = EvaluationInput { - action: input.action, - actor: input.actor, - snapshot: input.snapshot, - }; + let eval_input = EvaluationInput { + action: input.action, + actor: input.actor, + snapshot: input.snapshot, + }; - let result = evaluate(compiled_policy.as_ref(), &eval_input); + let result = evaluate(compiled_policy.as_ref(), &eval_input); - Ok(JsEvaluationOutput { - effect: effect_to_str(&result.effect).to_string(), - matched_rule: result.matched_rule.unwrap_or_default(), + Ok(JsEvaluationOutput { + effect: effect_to_str(&result.effect).to_string(), + matched_rule: result.matched_rule.unwrap_or_default(), + }) }) -}) } #[no_mangle] @@ -294,21 +287,19 @@ pub extern "C" fn actra_write_buffer(len: usize) -> *mut u8 { #[no_mangle] pub extern "C" fn actra_policy_hash(instance_id: i32) -> u64 { safe_exec(|| { - if instance_id < 0 { - return Err("invalid instance_id".to_string()); - } + if instance_id < 0 { + return Err("invalid instance_id".to_string()); + } - let compiled_policy = { - let instances = get_instances().lock().unwrap(); + let instances = get_instances().read().unwrap(); - match instances.get(instance_id as usize) { - Some(Some(i)) => Arc::clone(&i.compiled_policy), - _ => return Err("invalid instance_id".to_string()), - } - }; + let compiled_policy = match instances.get(instance_id as usize) { + Some(i) => Arc::clone(&i.compiled_policy), + None => return Err("invalid instance_id".to_string()), + }; - Ok(compiled_policy.policy_hash()) -}) + Ok(compiled_policy.policy_hash()) + }) } #[no_mangle] diff --git a/sdk/js/actra/package.json b/sdk/js/actra/package.json index 328d636..d44f834 100644 --- a/sdk/js/actra/package.json +++ b/sdk/js/actra/package.json @@ -1,6 +1,6 @@ { "name": "@getactra/actra", - "version": "0.7.7", + "version": "0.7.8", "description": "Actra JavaScript SDK for server and edge runtimes", "type": "module", "main": "./dist/node-entry.js",