summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDivy Srivastava <dj.srivastava23@gmail.com>2021-10-05 18:20:00 +0530
committerGitHub <noreply@github.com>2021-10-05 14:50:00 +0200
commit80aee99c9e2eba879a0981235bee740088c6fbad (patch)
tree68fd3a256b1900f141c105b1ab051eded2e95a6c
parentf1d3a170430501b4fab1a2d2abb5d77528251c77 (diff)
feat(ext/ffi): Non-blocking FFI (#12274)
-rw-r--r--Cargo.lock1
-rw-r--r--ext/ffi/00_ffi.js11
-rw-r--r--ext/ffi/Cargo.toml1
-rw-r--r--ext/ffi/lib.rs60
-rw-r--r--test_ffi/src/lib.rs9
-rw-r--r--test_ffi/tests/integration_tests.rs4
-rw-r--r--test_ffi/tests/test.js34
7 files changed, 95 insertions, 25 deletions
diff --git a/Cargo.lock b/Cargo.lock
index dc0deec73..0ce5b480c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -778,6 +778,7 @@ dependencies = [
"deno_core",
"dlopen",
"serde",
+ "tokio",
]
[[package]]
diff --git a/ext/ffi/00_ffi.js b/ext/ffi/00_ffi.js
index 553ea1dd3..c7fdd0e8c 100644
--- a/ext/ffi/00_ffi.js
+++ b/ext/ffi/00_ffi.js
@@ -13,8 +13,15 @@
this.#rid = core.opSync("op_ffi_load", { path, symbols });
for (const symbol in symbols) {
- this.symbols[symbol] = (...parameters) =>
- core.opSync("op_ffi_call", { rid: this.#rid, symbol, parameters });
+ this.symbols[symbol] = symbols[symbol].nonblocking
+ ? (...parameters) =>
+ core.opAsync("op_ffi_call_nonblocking", {
+ rid: this.#rid,
+ symbol,
+ parameters,
+ })
+ : (...parameters) =>
+ core.opSync("op_ffi_call", { rid: this.#rid, symbol, parameters });
}
}
diff --git a/ext/ffi/Cargo.toml b/ext/ffi/Cargo.toml
index 398aa5995..6d6357562 100644
--- a/ext/ffi/Cargo.toml
+++ b/ext/ffi/Cargo.toml
@@ -18,3 +18,4 @@ deno_core = { version = "0.101.0", path = "../../core" }
dlopen = "0.1.8"
libffi = { version = "=0.0.7", package = "deno-libffi" }
serde = { version = "1.0.129", features = ["derive"] }
+tokio = { version = "1.10.1", features = ["full"] }
diff --git a/ext/ffi/lib.rs b/ext/ffi/lib.rs
index e0430366c..e1acd097a 100644
--- a/ext/ffi/lib.rs
+++ b/ext/ffi/lib.rs
@@ -3,6 +3,7 @@
use deno_core::error::bad_resource_id;
use deno_core::error::AnyError;
use deno_core::include_js_files;
+use deno_core::op_async;
use deno_core::op_sync;
use deno_core::serde_json::json;
use deno_core::serde_json::Value;
@@ -14,6 +15,7 @@ use dlopen::raw::Library;
use libffi::middle::Arg;
use serde::Deserialize;
use std::borrow::Cow;
+use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::ffi::c_void;
@@ -37,6 +39,7 @@ pub trait FfiPermissions {
fn check(&mut self, path: &str) -> Result<(), AnyError>;
}
+#[derive(Clone)]
struct Symbol {
cif: libffi::middle::Cif,
ptr: libffi::middle::CodePtr,
@@ -44,6 +47,9 @@ struct Symbol {
result_type: NativeType,
}
+unsafe impl Send for Symbol {}
+unsafe impl Sync for Symbol {}
+
struct DynamicLibraryResource {
lib: Library,
symbols: HashMap<String, Symbol>,
@@ -99,6 +105,7 @@ pub fn init<P: FfiPermissions + 'static>(unstable: bool) -> Extension {
.ops(vec![
("op_ffi_load", op_sync(op_ffi_load::<P>)),
("op_ffi_call", op_sync(op_ffi_call)),
+ ("op_ffi_call_nonblocking", op_async(op_ffi_call_nonblocking)),
])
.state(move |state| {
// Stolen from deno_webgpu, is there a better option?
@@ -294,20 +301,7 @@ struct FfiCallArgs {
parameters: Vec<Value>,
}
-fn op_ffi_call(
- state: &mut deno_core::OpState,
- args: FfiCallArgs,
- _: (),
-) -> Result<Value, AnyError> {
- let resource = state
- .resource_table
- .get::<DynamicLibraryResource>(args.rid)?;
-
- let symbol = resource
- .symbols
- .get(&args.symbol)
- .ok_or_else(bad_resource_id)?;
-
+fn ffi_call(args: FfiCallArgs, symbol: &Symbol) -> Result<Value, AnyError> {
let native_values = symbol
.parameter_types
.iter()
@@ -366,3 +360,41 @@ fn op_ffi_call(
}
})
}
+
+fn op_ffi_call(
+ state: &mut deno_core::OpState,
+ args: FfiCallArgs,
+ _: (),
+) -> Result<Value, AnyError> {
+ let resource = state
+ .resource_table
+ .get::<DynamicLibraryResource>(args.rid)?;
+
+ let symbol = resource
+ .symbols
+ .get(&args.symbol)
+ .ok_or_else(bad_resource_id)?;
+
+ ffi_call(args, symbol)
+}
+
+/// A non-blocking FFI call.
+async fn op_ffi_call_nonblocking(
+ state: Rc<RefCell<deno_core::OpState>>,
+ args: FfiCallArgs,
+ _: (),
+) -> Result<Value, AnyError> {
+ let resource = state
+ .borrow()
+ .resource_table
+ .get::<DynamicLibraryResource>(args.rid)?;
+ let symbols = &resource.symbols;
+ let symbol = symbols
+ .get(&args.symbol)
+ .ok_or_else(bad_resource_id)?
+ .clone();
+
+ tokio::task::spawn_blocking(move || ffi_call(args, &symbol))
+ .await
+ .unwrap()
+}
diff --git a/test_ffi/src/lib.rs b/test_ffi/src/lib.rs
index a5a077950..c91d05e05 100644
--- a/test_ffi/src/lib.rs
+++ b/test_ffi/src/lib.rs
@@ -1,3 +1,6 @@
+use std::thread::sleep;
+use std::time::Duration;
+
#[no_mangle]
pub extern "C" fn print_something() {
println!("something");
@@ -42,3 +45,9 @@ pub extern "C" fn add_f32(a: f32, b: f32) -> f32 {
pub extern "C" fn add_f64(a: f64, b: f64) -> f64 {
a + b
}
+
+#[no_mangle]
+pub extern "C" fn sleep_blocking(ms: u64) {
+ let duration = Duration::from_millis(ms);
+ sleep(duration);
+}
diff --git a/test_ffi/tests/integration_tests.rs b/test_ffi/tests/integration_tests.rs
index 62b28d879..0b2eae854 100644
--- a/test_ffi/tests/integration_tests.rs
+++ b/test_ffi/tests/integration_tests.rs
@@ -46,6 +46,10 @@ fn basic() {
579\n\
579.9119873046875\n\
579.912\n\
+ Before\n\
+ true\n\
+ After\n\
+ true\n\
Correct number of resources\n";
assert_eq!(stdout, expected);
assert_eq!(stderr, "");
diff --git a/test_ffi/tests/test.js b/test_ffi/tests/test.js
index 24b64722c..09c5f7417 100644
--- a/test_ffi/tests/test.js
+++ b/test_ffi/tests/test.js
@@ -20,6 +20,7 @@ const dylib = Deno.dlopen(libPath, {
"add_isize": { parameters: ["isize", "isize"], result: "isize" },
"add_f32": { parameters: ["f32", "f32"], result: "f32" },
"add_f64": { parameters: ["f64", "f64"], result: "f64" },
+ "sleep_blocking": { parameters: ["u64"], result: "void", nonblocking: true },
});
dylib.symbols.print_something();
@@ -32,16 +33,31 @@ console.log(dylib.symbols.add_isize(123, 456));
console.log(dylib.symbols.add_f32(123.123, 456.789));
console.log(dylib.symbols.add_f64(123.123, 456.789));
-dylib.close();
-const resourcesPost = Deno.resources();
+// Test non blocking calls
+const start = performance.now();
+dylib.symbols.sleep_blocking(100).then(() => {
+ console.log("After");
+ console.log(performance.now() - start >= 100);
+ // Close after task is complete.
+ cleanup();
+});
+console.log("Before");
+console.log(performance.now() - start < 100);
+
+function cleanup() {
+ dylib.close();
-const preStr = JSON.stringify(resourcesPre, null, 2);
-const postStr = JSON.stringify(resourcesPost, null, 2);
-if (preStr !== postStr) {
- throw new Error(
- `Difference in open resources before dlopen and after closing:
+ const resourcesPost = Deno.resources();
+
+ const preStr = JSON.stringify(resourcesPre, null, 2);
+ const postStr = JSON.stringify(resourcesPost, null, 2);
+ if (preStr !== postStr) {
+ throw new Error(
+ `Difference in open resources before dlopen and after closing:
Before: ${preStr}
After: ${postStr}`,
- );
+ );
+ }
+
+ console.log("Correct number of resources");
}
-console.log("Correct number of resources");