mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 17:34:47 -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 {
|
import { assertStringIncludes, unitTest, unreachable } from "./test_util.ts";
|
||||||
assert,
|
|
||||||
assertEquals,
|
|
||||||
assertMatch,
|
|
||||||
unitTest,
|
|
||||||
unreachable,
|
|
||||||
} from "./test_util.ts";
|
|
||||||
|
|
||||||
const readErrorStackPattern = new RegExp(
|
unitTest(async function sendAsyncStackTrace() {
|
||||||
`^.*
|
|
||||||
at processErr \\(.*core\\.js:.*\\)
|
|
||||||
at opAsyncHandler \\(.*core\\.js:.*\\)
|
|
||||||
at handleAsyncMsgFromRust \\(.*core\\.js:.*\\).*$`,
|
|
||||||
"ms",
|
|
||||||
);
|
|
||||||
|
|
||||||
unitTest(async function sendAsyncStackTrace(): Promise<void> {
|
|
||||||
const buf = new Uint8Array(10);
|
const buf = new Uint8Array(10);
|
||||||
const rid = 10;
|
const rid = 10;
|
||||||
try {
|
try {
|
||||||
await Deno.read(rid, buf);
|
await Deno.read(rid, buf);
|
||||||
unreachable();
|
unreachable();
|
||||||
} catch (error) {
|
} 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() {
|
function handleAsyncMsgFromRust() {
|
||||||
for (let i = 0; i < arguments.length; i += 2) {
|
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, []];
|
return errorMap[errorName] ?? [undefined, []];
|
||||||
}
|
}
|
||||||
|
|
||||||
function processResponse(res) {
|
function unwrapOpResult(res) {
|
||||||
if (!isErr(res)) {
|
// .$err_class_name is a special key that should only exist on errors
|
||||||
return res;
|
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);
|
return 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function jsonOpAsync(opName, args = null, zeroCopy = null) {
|
function jsonOpAsync(opName, args = null, zeroCopy = null) {
|
||||||
const promiseId = nextPromiseId++;
|
const promiseId = nextPromiseId++;
|
||||||
const maybeError = dispatch(opName, promiseId, args, zeroCopy);
|
const maybeError = dispatch(opName, promiseId, args, zeroCopy);
|
||||||
// Handle sync error (e.g: error parsing args)
|
// Handle sync error (e.g: error parsing args)
|
||||||
if (maybeError) processResponse(maybeError);
|
if (maybeError) return unwrapOpResult(maybeError);
|
||||||
return setPromise(promiseId);
|
return setPromise(promiseId).then(unwrapOpResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
function jsonOpSync(opName, args = null, zeroCopy = null) {
|
function jsonOpSync(opName, args = null, zeroCopy = null) {
|
||||||
return processResponse(dispatch(opName, null, args, zeroCopy));
|
return unwrapOpResult(dispatch(opName, null, args, zeroCopy));
|
||||||
}
|
|
||||||
|
|
||||||
function opAsyncHandler(promiseId, res) {
|
|
||||||
const promise = getPromise(promiseId);
|
|
||||||
if (!isErr(res)) {
|
|
||||||
promise.resolve(res);
|
|
||||||
} else {
|
|
||||||
promise.reject(processErr(res));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function binOpSync(opName, args = null, zeroCopy = null) {
|
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