0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 09:31:22 -05:00

fix: async op error stacktraces (#10080)

Co-authored-by: Aaron O'Mullan <aaron.omullan@gmail.com>
This commit is contained in:
Nayeem Rahman 2021-04-09 16:55:33 +01:00 committed by GitHub
parent c6e7a243d5
commit c86ee742a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 50 deletions

View file

@ -1,27 +1,16 @@
import {
assert,
assertEquals,
assertMatch,
unitTest,
unreachable,
} from "./test_util.ts";
import { assertStringIncludes, unitTest, unreachable } from "./test_util.ts";
const readErrorStackPattern = new RegExp(
`^.*
at processErr \\(.*core\\.js:.*\\)
at opAsyncHandler \\(.*core\\.js:.*\\)
at handleAsyncMsgFromRust \\(.*core\\.js:.*\\).*$`,
"ms",
);
unitTest(async function sendAsyncStackTrace(): Promise<void> {
unitTest(async function sendAsyncStackTrace() {
const buf = new Uint8Array(10);
const rid = 10;
try {
await Deno.read(rid, buf);
unreachable();
} catch (error) {
assertMatch(error.stack, readErrorStackPattern);
const s = error.stack.toString();
console.log(s);
assertStringIncludes(s, "dispatch_bin_test.ts");
assertStringIncludes(s, "read");
}
});

View file

@ -65,7 +65,10 @@
function handleAsyncMsgFromRust() {
for (let i = 0; i < arguments.length; i += 2) {
opAsyncHandler(arguments[i], arguments[i + 1]);
const promiseId = arguments[i];
const res = arguments[i + 1];
const promise = getPromise(promiseId);
promise.resolve(res);
}
}
@ -84,48 +87,31 @@
return errorMap[errorName] ?? [undefined, []];
}
function processResponse(res) {
if (!isErr(res)) {
return res;
function unwrapOpResult(res) {
// .$err_class_name is a special key that should only exist on errors
if (res?.$err_class_name) {
const className = res.$err_class_name;
const [ErrorClass, args] = getErrorClassAndArgs(className);
if (!ErrorClass) {
throw new Error(
`Unregistered error class: "${className}"\n ${res.message}\n Classes of errors returned from ops should be registered via Deno.core.registerErrorClass().`,
);
}
throw new ErrorClass(res.message, ...args);
}
throw processErr(res);
}
// .$err_class_name is a special key that should only exist on errors
function isErr(res) {
return !!(res && res.$err_class_name);
}
function processErr(err) {
const className = err.$err_class_name;
const [ErrorClass, args] = getErrorClassAndArgs(className);
if (!ErrorClass) {
return new Error(
`Unregistered error class: "${className}"\n ${err.message}\n Classes of errors returned from ops should be registered via Deno.core.registerErrorClass().`,
);
}
return new ErrorClass(err.message, ...args);
return res;
}
function jsonOpAsync(opName, args = null, zeroCopy = null) {
const promiseId = nextPromiseId++;
const maybeError = dispatch(opName, promiseId, args, zeroCopy);
// Handle sync error (e.g: error parsing args)
if (maybeError) processResponse(maybeError);
return setPromise(promiseId);
if (maybeError) return unwrapOpResult(maybeError);
return setPromise(promiseId).then(unwrapOpResult);
}
function jsonOpSync(opName, args = null, zeroCopy = null) {
return processResponse(dispatch(opName, null, args, zeroCopy));
}
function opAsyncHandler(promiseId, res) {
const promise = getPromise(promiseId);
if (!isErr(res)) {
promise.resolve(res);
} else {
promise.reject(processErr(res));
}
return unwrapOpResult(dispatch(opName, null, args, zeroCopy));
}
function binOpSync(opName, args = null, zeroCopy = null) {

View file

@ -108,3 +108,51 @@ where
},
)
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn json_op_async_stack_trace() {
let mut runtime = crate::JsRuntime::new(Default::default());
async fn op_throw(
_state: Rc<RefCell<OpState>>,
msg: Option<String>,
zero_copy: Option<ZeroCopyBuf>,
) -> Result<(), AnyError> {
assert_eq!(msg.unwrap(), "hello");
assert!(zero_copy.is_none());
Err(crate::error::generic_error("foo"))
}
runtime.register_op("op_throw", json_op_async(op_throw));
runtime
.execute(
"<init>",
r#"
// First we initialize the ops cache. This maps op names to their id's.
Deno.core.ops();
// Register the error class.
Deno.core.registerErrorClass('Error', Error);
async function f1() {
await Deno.core.jsonOpAsync('op_throw', 'hello');
}
async function f2() {
await f1();
}
f2();
"#,
)
.unwrap();
let e = runtime.run_event_loop().await.unwrap_err().to_string();
println!("{}", e);
assert!(e.contains("Error: foo"));
assert!(e.contains("at async f1 (<init>:"));
assert!(e.contains("at async f2 (<init>:"));
}
}