deno/test_ffi/tests/ffi_callback_errors.ts
2024-01-01 19:58:21 +00:00

142 lines
3.3 KiB
TypeScript

// Copyright 2018-2024 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,
},
);
}