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:
parent
c6e7a243d5
commit
c86ee742a2
3 changed files with 73 additions and 50 deletions
|
@ -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");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
52
core/core.js
52
core/core.js
|
@ -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) {
|
||||
|
|
|
@ -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>:"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue