summaryrefslogtreecommitdiff
path: root/test_ffi
diff options
context:
space:
mode:
Diffstat (limited to 'test_ffi')
-rw-r--r--test_ffi/src/lib.rs13
-rw-r--r--test_ffi/tests/integration_tests.rs40
-rw-r--r--test_ffi/tests/test.js41
-rw-r--r--test_ffi/tests/thread_safe_test.js101
-rw-r--r--test_ffi/tests/thread_safe_test_worker.js41
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");
+});