0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-10-29 08:58:01 -04:00
denoland-deno/core/01_core.js
Bartek Iwańczuk d332bf1132
feat: deno test --trace-ops (#13770)
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.
2022-02-25 16:14:46 +01:00

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);