Skip to content

lazy_static rhai Engine #112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ opt-level = 0
[dev-dependencies]
async-std = { version = "1.5.0", features = [ "attributes" ] }
tokio = { version = "0.2.11", features = [ "full" ] }
futures = "0.3.4"
54 changes: 51 additions & 3 deletions benches/benchmark.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
#![feature(test)]
use casbin::prelude::*;

extern crate test;

use test::Bencher;

use casbin::prelude::*;

fn await_future<F, T>(future: F) -> T
where
F: std::future::Future<Output = T>,
Expand Down Expand Up @@ -479,3 +478,52 @@ fn b_benchmark_cached_priority_model(b: &mut Bencher) {

b.iter(|| await_future(e.enforce(&["alice", "data1", "read"])).unwrap());
}

#[allow(dead_code)]
mod task {
use std::future::Future;
use std::pin::Pin;
use std::ptr;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};

const RAW_WAKER: RawWaker = RawWaker::new(ptr::null(), &VTABLE);
const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);

unsafe fn clone(_: *const ()) -> RawWaker {
RAW_WAKER
}

unsafe fn wake(_: *const ()) {}

unsafe fn wake_by_ref(_: *const ()) {}

unsafe fn drop(_: *const ()) {}

pub fn create() -> Waker {
// Safety: The waker points to a vtable with functions that do nothing. Doing
// nothing is always safe.
unsafe { Waker::from_raw(RAW_WAKER) }
}

/// Using this tests only the code that is polled.
///
/// `block_on` creates no runtime overhead so a more accurate benchmark of just the code
/// can be collected.
pub fn block_on<F, T>(mut future: F) -> T
where
F: Future<Output = T>,
{
// Safety: since we own the future no one can move any part of it but us, and we won't.
let mut fut = unsafe { Pin::new_unchecked(&mut future) };
let waker = create();
let mut ctx = Context::from_waker(&waker);
loop {
if let Poll::Ready(res) = fut.as_mut().poll(&mut ctx) {
return res;
}
// TODO since criterion is single threaded simply looping seems ok
// burning cpu for a simpler function seems fair
// possible `std::sync::atomic::spin_loop_hint` here.
}
}
}
98 changes: 95 additions & 3 deletions src/enforcer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{
};

use async_trait::async_trait;
use lazy_static::lazy_static;
use rhai::{Array, Engine, RegisterFn, Scope};

use std::{
Expand Down Expand Up @@ -223,11 +224,15 @@ impl CoreApi for Enforcer {
/// fn main() {}
/// ```
async fn enforce<S: AsRef<str> + Send + Sync>(&mut self, rvals: &[S]) -> Result<bool> {
lazy_static! {
static ref ENGINE: Arc<async_std::sync::RwLock<Engine<'static>>> = Arc::new(async_std::sync::RwLock::new(Engine::new()));
}

if !self.enabled {
return Ok(true);
}

let mut engine = Engine::new();
let mut engine = ENGINE.try_write().unwrap();
let mut scope: Scope = Scope::new();

let r_ast = get_or_err!(self, "r", ModelError::R, "request");
Expand Down Expand Up @@ -387,6 +392,92 @@ mod tests {
assert!(is_send::<Enforcer>());
assert!(is_sync::<Enforcer>());
}

#[test]
#[ignore]
fn test_enforcer_deadlock() {
lazy_static! {
static ref E: Arc<async_std::sync::RwLock<Enforcer>> = {
let mut m = DefaultModel::default();
m.add_def("r", "r", "sub, obj, act");
m.add_def("p", "p", "sub, obj, act");
m.add_def("e", "e", "some(where (p.eft == allow))");
m.add_def(
"m",
"m",
"r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)",
);
let file = FileAdapter::new("examples/keymatch_policy.csv");
Arc::new(async_std::sync::RwLock::new(async_std::task::block_on(Enforcer::new(m, file)).unwrap()))
};
}

lazy_static! {
static ref E2: Arc<RwLock<Enforcer>> = {
let mut m = DefaultModel::default();
m.add_def("r", "r", "sub, obj, act");
m.add_def("p", "p", "sub, obj, act");
m.add_def("e", "e", "some(where (p.eft == allow))");
m.add_def(
"m",
"m",
"r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)",
);
let file = FileAdapter::new("examples/keymatch_policy.csv");
Arc::new(RwLock::new(async_std::task::block_on(Enforcer::new(m, file)).unwrap()))
};
}

const V: &'static [&str] = &["alice", "/alice_data/resource1", "GET"];

// run a bunch of times to test for deadlocks
for _ in 0..1000 {
let mut a = vec![];
async_std::task::block_on(async {
for _ in 0..10_usize {
a.push(async_std::task::spawn(async move {
let mut e = E.write().await;
assert!(e.enforce(V).await.unwrap());
}));
}
futures::join!(
async_std::task::spawn(async move {
let mut e = E.write().await;
assert!(e.enforce(V).await.unwrap());
}),
async_std::task::spawn(async move {
let mut e = E.write().await;
assert!(e.enforce(V).await.unwrap());
}),
async_std::task::spawn(async move {
let mut e = E.write().await;
assert!(e.enforce(V).await.unwrap());
}),
async_std::task::spawn(async move {
let mut e = E.write().await;
assert!(e.enforce(V).await.unwrap());
}),
);

for x in a {
x.await;
}
});

let mut threads = vec![];
for _ in 0..10_usize {
threads.push(std::thread::spawn(move || {
async_std::task::block_on(async {
let mut e = E2.write().unwrap();
assert!(e.enforce(V).await.unwrap());
});
}));
}
for t in threads {
t.join().unwrap();
}
}
}

#[cfg_attr(feature = "runtime-async-std", async_std::test)]
#[cfg_attr(feature = "runtime-tokio", tokio::test)]
Expand All @@ -409,7 +500,8 @@ mod tests {
.adapter
.add_policy("p", "p", vec!["alice".into(), "data".into(), "read".into()])
.await
.unwrap());
.unwrap()
);
e.set_adapter(mem).await.unwrap();
// this passes since our MemoryAdapter has a working add_policy method
assert!(e
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![recursion_limit="256"]

mod adapter;
mod cache;
mod cached_api;
Expand Down