mirror of
https://github.com/denoland/deno.git
synced 2024-11-29 16:30:56 -05:00
d332bf1132
This commit adds "--trace-ops" flag to "deno test" subcommand. This flag enables saving of stack traces for async ops, that before were always saved. While the feature proved to be very useful it comes with a significant performance hit, it's caused by excessive source mapping of stack frames.
259 lines
7.1 KiB
JavaScript
259 lines
7.1 KiB
JavaScript
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
|
"use strict";
|
|
|
|
((window) => {
|
|
const {
|
|
Error,
|
|
RangeError,
|
|
ReferenceError,
|
|
SyntaxError,
|
|
TypeError,
|
|
URIError,
|
|
Map,
|
|
Array,
|
|
ArrayPrototypeFill,
|
|
ArrayPrototypeMap,
|
|
ErrorCaptureStackTrace,
|
|
Promise,
|
|
ObjectEntries,
|
|
ObjectFreeze,
|
|
ObjectFromEntries,
|
|
MapPrototypeGet,
|
|
MapPrototypeDelete,
|
|
MapPrototypeSet,
|
|
PromisePrototypeThen,
|
|
PromisePrototypeFinally,
|
|
StringPrototypeSlice,
|
|
ObjectAssign,
|
|
SymbolFor,
|
|
} = window.__bootstrap.primordials;
|
|
|
|
// Available on start due to bindings.
|
|
const { opcallSync, opcallAsync } = window.Deno.core;
|
|
|
|
let opsCache = {};
|
|
const errorMap = {};
|
|
// Builtin v8 / JS errors
|
|
registerErrorClass("Error", Error);
|
|
registerErrorClass("RangeError", RangeError);
|
|
registerErrorClass("ReferenceError", ReferenceError);
|
|
registerErrorClass("SyntaxError", SyntaxError);
|
|
registerErrorClass("TypeError", TypeError);
|
|
registerErrorClass("URIError", URIError);
|
|
|
|
let nextPromiseId = 1;
|
|
const promiseMap = new Map();
|
|
const RING_SIZE = 4 * 1024;
|
|
const NO_PROMISE = null; // Alias to null is faster than plain nulls
|
|
const promiseRing = ArrayPrototypeFill(new Array(RING_SIZE), NO_PROMISE);
|
|
// TODO(bartlomieju): it future use `v8::Private` so it's not visible
|
|
// to users. Currently missing bindings.
|
|
const promiseIdSymbol = SymbolFor("Deno.core.internalPromiseId");
|
|
|
|
let opCallTracingEnabled = false;
|
|
const opCallTraces = new Map();
|
|
|
|
function enableOpCallTracing() {
|
|
opCallTracingEnabled = true;
|
|
}
|
|
|
|
function isOpCallTracingEnabled() {
|
|
return opCallTracingEnabled;
|
|
}
|
|
|
|
function setPromise(promiseId) {
|
|
const idx = promiseId % RING_SIZE;
|
|
// Move old promise from ring to map
|
|
const oldPromise = promiseRing[idx];
|
|
if (oldPromise !== NO_PROMISE) {
|
|
const oldPromiseId = promiseId - RING_SIZE;
|
|
MapPrototypeSet(promiseMap, oldPromiseId, oldPromise);
|
|
}
|
|
// Set new promise
|
|
return promiseRing[idx] = newPromise();
|
|
}
|
|
|
|
function getPromise(promiseId) {
|
|
// Check if out of ring bounds, fallback to map
|
|
const outOfBounds = promiseId < nextPromiseId - RING_SIZE;
|
|
if (outOfBounds) {
|
|
const promise = MapPrototypeGet(promiseMap, promiseId);
|
|
MapPrototypeDelete(promiseMap, promiseId);
|
|
return promise;
|
|
}
|
|
// Otherwise take from ring
|
|
const idx = promiseId % RING_SIZE;
|
|
const promise = promiseRing[idx];
|
|
promiseRing[idx] = NO_PROMISE;
|
|
return promise;
|
|
}
|
|
|
|
function newPromise() {
|
|
let resolve, reject;
|
|
const promise = new Promise((resolve_, reject_) => {
|
|
resolve = resolve_;
|
|
reject = reject_;
|
|
});
|
|
promise.resolve = resolve;
|
|
promise.reject = reject;
|
|
return promise;
|
|
}
|
|
|
|
function ops() {
|
|
return opsCache;
|
|
}
|
|
|
|
function syncOpsCache() {
|
|
// op id 0 is a special value to retrieve the map of registered ops.
|
|
opsCache = ObjectFreeze(ObjectFromEntries(opcallSync(0)));
|
|
}
|
|
|
|
function opresolve() {
|
|
for (let i = 0; i < arguments.length; i += 2) {
|
|
const promiseId = arguments[i];
|
|
const res = arguments[i + 1];
|
|
const promise = getPromise(promiseId);
|
|
promise.resolve(res);
|
|
}
|
|
}
|
|
|
|
function registerErrorClass(className, errorClass) {
|
|
registerErrorBuilder(className, (msg) => new errorClass(msg));
|
|
}
|
|
|
|
function registerErrorBuilder(className, errorBuilder) {
|
|
if (typeof errorMap[className] !== "undefined") {
|
|
throw new TypeError(`Error class for "${className}" already registered`);
|
|
}
|
|
errorMap[className] = errorBuilder;
|
|
}
|
|
|
|
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 errorBuilder = errorMap[className];
|
|
const err = errorBuilder ? errorBuilder(res.message) : new Error(
|
|
`Unregistered error class: "${className}"\n ${res.message}\n Classes of errors returned from ops should be registered via Deno.core.registerErrorClass().`,
|
|
);
|
|
// Set .code if error was a known OS error, see error_codes.rs
|
|
if (res.code) {
|
|
err.code = res.code;
|
|
}
|
|
// Strip unwrapOpResult() and errorBuilder() calls from stack trace
|
|
ErrorCaptureStackTrace(err, unwrapOpResult);
|
|
throw err;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
function opAsync(opName, arg1 = null, arg2 = null) {
|
|
const promiseId = nextPromiseId++;
|
|
const maybeError = opcallAsync(opsCache[opName], promiseId, arg1, arg2);
|
|
// Handle sync error (e.g: error parsing args)
|
|
if (maybeError) return unwrapOpResult(maybeError);
|
|
let p = PromisePrototypeThen(setPromise(promiseId), unwrapOpResult);
|
|
if (opCallTracingEnabled) {
|
|
// Capture a stack trace by creating a new `Error` object. We remove the
|
|
// first 6 characters (the `Error\n` prefix) to get just the stack trace.
|
|
const stack = StringPrototypeSlice(new Error().stack, 6);
|
|
MapPrototypeSet(opCallTraces, promiseId, { opName, stack });
|
|
p = PromisePrototypeFinally(
|
|
p,
|
|
() => MapPrototypeDelete(opCallTraces, promiseId),
|
|
);
|
|
}
|
|
// Save the id on the promise so it can later be ref'ed or unref'ed
|
|
p[promiseIdSymbol] = promiseId;
|
|
return p;
|
|
}
|
|
|
|
function opSync(opName, arg1 = null, arg2 = null) {
|
|
return unwrapOpResult(opcallSync(opsCache[opName], arg1, arg2));
|
|
}
|
|
|
|
function resources() {
|
|
return ObjectFromEntries(opSync("op_resources"));
|
|
}
|
|
|
|
function read(rid, buf) {
|
|
return opAsync("op_read", rid, buf);
|
|
}
|
|
|
|
function write(rid, buf) {
|
|
return opAsync("op_write", rid, buf);
|
|
}
|
|
|
|
function shutdown(rid) {
|
|
return opAsync("op_shutdown", rid);
|
|
}
|
|
|
|
function close(rid) {
|
|
opSync("op_close", rid);
|
|
}
|
|
|
|
function tryClose(rid) {
|
|
opSync("op_try_close", rid);
|
|
}
|
|
|
|
function print(str, isErr = false) {
|
|
opSync("op_print", str, isErr);
|
|
}
|
|
|
|
function metrics() {
|
|
const [aggregate, perOps] = opSync("op_metrics");
|
|
aggregate.ops = ObjectFromEntries(ArrayPrototypeMap(
|
|
ObjectEntries(opsCache),
|
|
([opName, opId]) => [opName, perOps[opId]],
|
|
));
|
|
return aggregate;
|
|
}
|
|
|
|
// Some "extensions" rely on "BadResource" and "Interrupted" errors in the
|
|
// JS code (eg. "deno_net") so they are provided in "Deno.core" but later
|
|
// reexported on "Deno.errors"
|
|
class BadResource extends Error {
|
|
constructor(msg) {
|
|
super(msg);
|
|
this.name = "BadResource";
|
|
}
|
|
}
|
|
const BadResourcePrototype = BadResource.prototype;
|
|
|
|
class Interrupted extends Error {
|
|
constructor(msg) {
|
|
super(msg);
|
|
this.name = "Interrupted";
|
|
}
|
|
}
|
|
const InterruptedPrototype = Interrupted.prototype;
|
|
|
|
// Extra Deno.core.* exports
|
|
const core = ObjectAssign(globalThis.Deno.core, {
|
|
opAsync,
|
|
opSync,
|
|
ops,
|
|
close,
|
|
tryClose,
|
|
read,
|
|
write,
|
|
shutdown,
|
|
print,
|
|
resources,
|
|
metrics,
|
|
registerErrorBuilder,
|
|
registerErrorClass,
|
|
opresolve,
|
|
syncOpsCache,
|
|
BadResource,
|
|
BadResourcePrototype,
|
|
Interrupted,
|
|
InterruptedPrototype,
|
|
enableOpCallTracing,
|
|
isOpCallTracingEnabled,
|
|
opCallTraces,
|
|
});
|
|
|
|
ObjectAssign(globalThis.__bootstrap, { core });
|
|
ObjectAssign(globalThis.Deno, { core });
|
|
})(globalThis);
|