diff options
Diffstat (limited to 'test_ffi/tests/ffi_callback_errors.ts')
-rw-r--r-- | test_ffi/tests/ffi_callback_errors.ts | 141 |
1 files changed, 141 insertions, 0 deletions
diff --git a/test_ffi/tests/ffi_callback_errors.ts b/test_ffi/tests/ffi_callback_errors.ts new file mode 100644 index 000000000..dda4de5fb --- /dev/null +++ b/test_ffi/tests/ffi_callback_errors.ts @@ -0,0 +1,141 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +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_2: { + parameters: ["function"], + result: "void", + }, + call_stored_function_2: { + parameters: ["u8"], + result: "void", + }, + call_stored_function_2_from_other_thread: { + name: "call_stored_function_2", + parameters: ["u8"], + result: "void", + nonblocking: true, + }, + call_stored_function_2_thread_safe: { + parameters: ["u8"], + result: "void", + }, + } as const, +); + +globalThis.addEventListener("error", (data) => { + console.log("Unhandled error"); + data.preventDefault(); +}); +globalThis.onerror = (data) => { + console.log("Unhandled error"); + if (typeof data !== "string") { + data.preventDefault(); + } +}; + +globalThis.addEventListener("unhandledrejection", (data) => { + console.log("Unhandled rejection"); + data.preventDefault(); +}); + +const timer = setTimeout(() => { + console.error( + "Test failed, final callback did not get picked up by Deno event loop", + ); + Deno.exit(-1); +}, 5_000); + +Deno.unrefTimer(timer); + +enum CallCase { + SyncSelf, + SyncFfi, + AsyncSelf, + AsyncSyncFfi, + AsyncFfi, +} +type U8CallCase = Deno.NativeU8Enum<CallCase>; + +const throwCb = (c: CallCase): number => { + console.log("CallCase:", CallCase[c]); + if (c === CallCase.AsyncFfi) { + cb.unref(); + } + throw new Error("Error"); +}; + +const THROW_CB_DEFINITION = { + parameters: ["u8" as U8CallCase], + result: "u8", +} as const; + +const cb = new Deno.UnsafeCallback(THROW_CB_DEFINITION, throwCb); + +try { + const fnPointer = new Deno.UnsafeFnPointer(cb.pointer, THROW_CB_DEFINITION); + + fnPointer.call(CallCase.SyncSelf); +} catch (_err) { + console.log( + "Throwing errors from an UnsafeCallback called from a synchronous UnsafeFnPointer works. Terribly excellent.", + ); +} + +dylib.symbols.store_function_2(cb.pointer); +try { + dylib.symbols.call_stored_function_2(CallCase.SyncFfi); +} catch (_err) { + console.log( + "Throwing errors from an UnsafeCallback called from a synchronous FFI symbol works. Terribly excellent.", + ); +} + +try { + const fnPointer = new Deno.UnsafeFnPointer(cb.pointer, { + ...THROW_CB_DEFINITION, + nonblocking: true, + }); + await fnPointer.call(CallCase.AsyncSelf); +} catch (err) { + throw new Error( + "Nonblocking UnsafeFnPointer should not be threading through a JS error thrown on the other side of the call", + { + cause: err, + }, + ); +} + +try { + await dylib.symbols.call_stored_function_2_from_other_thread( + CallCase.AsyncSyncFfi, + ); +} catch (err) { + throw new Error( + "Nonblocking symbol call should not be threading through a JS error thrown on the other side of the call", + { + cause: err, + }, + ); +} +try { + // Ref the callback to make sure we do not exit before the call is done. + cb.ref(); + dylib.symbols.call_stored_function_2_thread_safe(CallCase.AsyncFfi); +} catch (err) { + throw new Error( + "Blocking symbol call should not be travelling 1.5 seconds forward in time to figure out that it call will trigger a JS error to be thrown", + { + cause: err, + }, + ); +} |