diff options
Diffstat (limited to 'test_ffi')
-rw-r--r-- | test_ffi/src/lib.rs | 13 | ||||
-rw-r--r-- | test_ffi/tests/integration_tests.rs | 40 | ||||
-rw-r--r-- | test_ffi/tests/test.js | 41 | ||||
-rw-r--r-- | test_ffi/tests/thread_safe_test.js | 101 | ||||
-rw-r--r-- | test_ffi/tests/thread_safe_test_worker.js | 41 |
5 files changed, 229 insertions, 7 deletions
diff --git a/test_ffi/src/lib.rs b/test_ffi/src/lib.rs index b4908d9cd..d6f29cbb8 100644 --- a/test_ffi/src/lib.rs +++ b/test_ffi/src/lib.rs @@ -211,6 +211,19 @@ pub extern "C" fn call_stored_function_2(arg: u8) { } } +#[no_mangle] +pub extern "C" fn call_stored_function_thread_safe() { + std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_millis(1500)); + unsafe { + if STORED_FUNCTION.is_none() { + return; + } + STORED_FUNCTION.unwrap()(); + } + }); +} + // FFI performance helper functions #[no_mangle] pub extern "C" fn nop() {} diff --git a/test_ffi/tests/integration_tests.rs b/test_ffi/tests/integration_tests.rs index 77bcc758e..35f37aa14 100644 --- a/test_ffi/tests/integration_tests.rs +++ b/test_ffi/tests/integration_tests.rs @@ -77,6 +77,8 @@ fn basic() { true\n\ Before\n\ true\n\ + After\n\ + true\n\ logCallback\n\ 1 -1 2 -2 3 -3 4n -4n 0.5 -0.5 1 2 3 4 5 6 7 8\n\ u8: 8\n\ @@ -85,12 +87,14 @@ fn basic() { 30\n\ STORED_FUNCTION cleared\n\ STORED_FUNCTION_2 cleared\n\ + Thread safe call counter: 0\n\ + logCallback\n\ + Thread safe call counter: 1\n\ + u8: 8\n\ Static u32: 42\n\ Static i64: -1242464576485n\n\ Static ptr: true\n\ Static ptr value: 42\n\ - After\n\ - true\n\ Correct number of resources\n"; assert_eq!(stdout, expected); assert_eq!(stderr, ""); @@ -118,3 +122,35 @@ fn symbol_types() { assert!(output.status.success()); assert_eq!(stderr, ""); } + +#[test] +fn thread_safe_callback() { + build(); + + let output = deno_cmd() + .arg("run") + .arg("--allow-ffi") + .arg("--allow-read") + .arg("--unstable") + .arg("--quiet") + .arg("tests/thread_safe_test.js") + .env("NO_COLOR", "1") + .output() + .unwrap(); + let stdout = std::str::from_utf8(&output.stdout).unwrap(); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + if !output.status.success() { + println!("stdout {}", stdout); + println!("stderr {}", stderr); + } + println!("{:?}", output.status); + assert!(output.status.success()); + let expected = "\ + Callback on main thread\n\ + Callback on worker thread\n\ + Calling callback, isolate should stay asleep until callback is called\n\ + Callback being called\n\ + Isolate should now exit\n"; + assert_eq!(stdout, expected); + assert_eq!(stderr, ""); +} diff --git a/test_ffi/tests/test.js b/test_ffi/tests/test.js index ab31dcb83..03c166a7c 100644 --- a/test_ffi/tests/test.js +++ b/test_ffi/tests/test.js @@ -130,6 +130,12 @@ const dylib = Deno.dlopen(libPath, { parameters: ["function"], result: "void", }, + call_fn_ptr_thread_safe: { + name: "call_fn_ptr", + parameters: ["function"], + result: "void", + nonblocking: true, + }, call_fn_ptr_many_parameters: { parameters: ["function"], result: "void", @@ -138,6 +144,11 @@ const dylib = Deno.dlopen(libPath, { parameters: ["function"], result: "void", }, + call_fn_ptr_return_u8_thread_safe: { + name: "call_fn_ptr_return_u8", + parameters: ["function"], + result: "void", + }, call_fn_ptr_return_buffer: { parameters: ["function"], result: "void", @@ -292,15 +303,16 @@ console.log("After sleep_blocking"); console.log(performance.now() - start >= 100); start = performance.now(); -dylib.symbols.sleep_nonblocking(100).then(() => { +const promise_2 = dylib.symbols.sleep_nonblocking(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); +// Await to make sure `sleep_nonblocking` calls and logs before we proceed +await promise_2; + // Test calls with callback parameters const logCallback = new Deno.UnsafeCallback( { parameters: [], result: "void" }, @@ -376,6 +388,24 @@ dylib.symbols.store_function(ptr(nestedCallback)); dylib.symbols.store_function(null); dylib.symbols.store_function_2(null); +let counter = 0; +const addToFooCallback = new Deno.UnsafeCallback({ + parameters: [], + result: "void", +}, () => counter++); + +// Test thread safe callbacks +console.log("Thread safe call counter:", counter); +addToFooCallback.ref(); +await dylib.symbols.call_fn_ptr_thread_safe(ptr(addToFooCallback)); +addToFooCallback.unref(); +logCallback.ref(); +await dylib.symbols.call_fn_ptr_thread_safe(ptr(logCallback)); +logCallback.unref(); +console.log("Thread safe call counter:", counter); +returnU8Callback.ref(); +await dylib.symbols.call_fn_ptr_return_u8_thread_safe(ptr(returnU8Callback)); + // Test statics console.log("Static u32:", dylib.symbols.static_u32); console.log("Static i64:", dylib.symbols.static_i64); @@ -386,7 +416,7 @@ console.log( const view = new Deno.UnsafePointerView(dylib.symbols.static_ptr); console.log("Static ptr value:", view.getUint32()); -function cleanup() { +(function cleanup() { dylib.close(); throwCallback.close(); logCallback.close(); @@ -395,6 +425,7 @@ function cleanup() { returnBufferCallback.close(); add10Callback.close(); nestedCallback.close(); + addToFooCallback.close(); const resourcesPost = Deno.resources(); @@ -409,4 +440,4 @@ After: ${postStr}`, } console.log("Correct number of resources"); -} +})(); diff --git a/test_ffi/tests/thread_safe_test.js b/test_ffi/tests/thread_safe_test.js new file mode 100644 index 000000000..e54114055 --- /dev/null +++ b/test_ffi/tests/thread_safe_test.js @@ -0,0 +1,101 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +// deno-lint-ignore-file + +const targetDir = Deno.execPath().replace(/[^\/\\]+$/, ""); +const [libPrefix, libSuffix] = { + darwin: ["lib", "dylib"], + linux: ["lib", "so"], + windows: ["", "dll"], +}[Deno.build.os]; +const libPath = `${targetDir}/${libPrefix}test_ffi.${libSuffix}`; + +const resourcesPre = Deno.resources(); + +const dylib = Deno.dlopen(libPath, { + store_function: { + parameters: ["function"], + result: "void", + }, + call_stored_function: { + parameters: [], + result: "void", + }, + call_stored_function_thread_safe: { + parameters: [], + result: "void", + }, +}); + +let resolveWorker; +let workerResponsePromise; + +const worker = new Worker( + new URL("./thread_safe_test_worker.js", import.meta.url).href, + { type: "module" }, +); + +worker.addEventListener("message", () => { + if (resolveWorker) { + resolveWorker(); + } +}); + +const sendWorkerMessage = async (data) => { + workerResponsePromise = new Promise((res) => { + resolveWorker = res; + }); + worker.postMessage(data); + await workerResponsePromise; +}; + +// Test step 1: Register main thread callback, trigger on worker thread + +const mainThreadCallback = new Deno.UnsafeCallback( + { parameters: [], result: "void" }, + () => { + console.log("Callback on main thread"); + }, +); + +mainThreadCallback.ref(); + +dylib.symbols.store_function(mainThreadCallback.pointer); + +await sendWorkerMessage("call"); + +// Test step 2: Register on worker thread, trigger on main thread + +await sendWorkerMessage("register"); + +dylib.symbols.call_stored_function(); + +// Unref both main and worker thread callbacks and terminate the wrorker: Note, the stored function pointer in lib is now dangling. + +mainThreadCallback.unref(); +await sendWorkerMessage("unref"); +worker.terminate(); + +// Test step 3: Register a callback that will be the only thing left keeping the isolate from exiting. +// Rely on it to keep Deno running until the callback comes in and unrefs the callback, after which Deno should exit. + +const cleanupCallback = new Deno.UnsafeCallback( + { parameters: [], result: "void" }, + () => { + console.log("Callback being called"); + Promise.resolve().then(() => cleanup()); + }, +); + +cleanupCallback.ref(); + +function cleanup() { + cleanupCallback.unref(); + console.log("Isolate should now exit"); +} + +dylib.symbols.store_function(cleanupCallback.pointer); + +console.log( + "Calling callback, isolate should stay asleep until callback is called", +); +dylib.symbols.call_stored_function_thread_safe(); diff --git a/test_ffi/tests/thread_safe_test_worker.js b/test_ffi/tests/thread_safe_test_worker.js new file mode 100644 index 000000000..067004469 --- /dev/null +++ b/test_ffi/tests/thread_safe_test_worker.js @@ -0,0 +1,41 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +// deno-lint-ignore-file + +const targetDir = Deno.execPath().replace(/[^\/\\]+$/, ""); +const [libPrefix, libSuffix] = { + darwin: ["lib", "dylib"], + linux: ["lib", "so"], + windows: ["", "dll"], +}[Deno.build.os]; +const libPath = `${targetDir}/${libPrefix}test_ffi.${libSuffix}`; + +const dylib = Deno.dlopen(libPath, { + store_function: { + parameters: ["function"], + result: "void", + }, + call_stored_function: { + parameters: [], + result: "void", + }, +}); + +const callback = new Deno.UnsafeCallback( + { parameters: [], result: "void" }, + () => { + console.log("Callback on worker thread"); + }, +); + +callback.ref(); + +self.addEventListener("message", ({ data }) => { + if (data === "register") { + dylib.symbols.store_function(callback.pointer); + } else if (data === "call") { + dylib.symbols.call_stored_function(); + } else if (data === "unref") { + callback.unref(); + } + self.postMessage("done"); +}); |