diff --git a/Cargo.lock b/Cargo.lock index dc0deec738ef20..0ce5b480cec9aa 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 553ea1dd3663b3..c7fdd0e8c58458 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 398aa5995795c3..6d63575621686d 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 e0430366cf880d..e1acd097a84324 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, @@ -99,6 +105,7 @@ pub fn init(unstable: bool) -> Extension { .ops(vec![ ("op_ffi_load", op_sync(op_ffi_load::

)), ("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, } -fn op_ffi_call( - state: &mut deno_core::OpState, - args: FfiCallArgs, - _: (), -) -> Result { - let resource = state - .resource_table - .get::(args.rid)?; - - let symbol = resource - .symbols - .get(&args.symbol) - .ok_or_else(bad_resource_id)?; - +fn ffi_call(args: FfiCallArgs, symbol: &Symbol) -> Result { 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 { + let resource = state + .resource_table + .get::(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>, + args: FfiCallArgs, + _: (), +) -> Result { + let resource = state + .borrow() + .resource_table + .get::(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 a5a07795028b5d..c91d05e0554a8f 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 62b28d879fafd0..0b2eae854cd9ab 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 24b64722ca87c9..09c5f74178105d 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");