mirror of
https://github.com/denoland/deno.git
synced 2024-10-31 09:14:20 -04:00
2865f39bec
This is another optimization to help improve the baseline overhead of async ops. It shaves off ~55ns/op or ~7% of the current total async op overhead. It achieves these gains by taking advantage of the sequential nature of promise IDs and optimistically stores them sequentially in a pre-allocated circular buffer and fallbacks to the promise Map for slow to resolve promises.
160 lines
4.3 KiB
JavaScript
160 lines
4.3 KiB
JavaScript
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
|
"use strict";
|
|
|
|
((window) => {
|
|
// Available on start due to bindings.
|
|
const core = window.Deno.core;
|
|
const { recv, send } = core;
|
|
|
|
let opsCache = {};
|
|
const errorMap = {};
|
|
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 = new Array(RING_SIZE).fill(NO_PROMISE);
|
|
|
|
function init() {
|
|
recv(handleAsyncMsgFromRust);
|
|
}
|
|
|
|
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;
|
|
promiseMap.set(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 = promiseMap.get(promiseId);
|
|
promiseMap.delete(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() {
|
|
// op id 0 is a special value to retrieve the map of registered ops.
|
|
const newOpsCache = Object.fromEntries(send(0));
|
|
opsCache = Object.freeze(newOpsCache);
|
|
return opsCache;
|
|
}
|
|
|
|
function handleAsyncMsgFromRust() {
|
|
for (let i = 0; i < arguments.length; i += 2) {
|
|
opAsyncHandler(arguments[i], arguments[i + 1]);
|
|
}
|
|
}
|
|
|
|
function dispatch(opName, promiseId, control, zeroCopy) {
|
|
return send(opsCache[opName], promiseId, control, zeroCopy);
|
|
}
|
|
|
|
function registerErrorClass(errorName, className, args) {
|
|
if (typeof errorMap[errorName] !== "undefined") {
|
|
throw new TypeError(`Error class for "${errorName}" already registered`);
|
|
}
|
|
errorMap[errorName] = [className, args ?? []];
|
|
}
|
|
|
|
function getErrorClassAndArgs(errorName) {
|
|
return errorMap[errorName] ?? [undefined, []];
|
|
}
|
|
|
|
function processResponse(res) {
|
|
if (!isErr(res)) {
|
|
return res;
|
|
}
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|
|
|
|
function binOpSync(opName, args = null, zeroCopy = null) {
|
|
return jsonOpSync(opName, args, zeroCopy);
|
|
}
|
|
|
|
function binOpAsync(opName, args = null, zeroCopy = null) {
|
|
return jsonOpAsync(opName, args, zeroCopy);
|
|
}
|
|
|
|
function resources() {
|
|
return Object.fromEntries(jsonOpSync("op_resources"));
|
|
}
|
|
|
|
function close(rid) {
|
|
jsonOpSync("op_close", rid);
|
|
}
|
|
|
|
Object.assign(window.Deno.core, {
|
|
binOpAsync,
|
|
binOpSync,
|
|
jsonOpAsync,
|
|
jsonOpSync,
|
|
dispatch: send,
|
|
dispatchByName: dispatch,
|
|
ops,
|
|
close,
|
|
resources,
|
|
registerErrorClass,
|
|
init,
|
|
});
|
|
})(this);
|