From 61a0dbbb08e9de2fc75ee2211913c882a078a9a2 Mon Sep 17 00:00:00 2001 From: Matheus Castro Date: Fri, 21 Oct 2022 00:09:55 -0300 Subject: [PATCH] Organizing code. --- packages/meteor/dynamics_nodejs.js | 367 +++++++++++++---------------- 1 file changed, 166 insertions(+), 201 deletions(-) diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index db02f9bcc7..e0c13b496d 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -1,136 +1,57 @@ -/** - * Fiber-aware implementation of dynamic scoping, for use on the server - * - * If we are using Fiber, we store/update/fetch the context from the current Fiber. - * Else, we fetch from the AsyncLocalStorage. - */ +// Implementation of dynamic scoping, for use on the server - with Fibers or AsyncLocalStorage -var Fiber = Npm.require('fibers'); +var Fiber = Meteor._isFibersEnabled && Npm.require('fibers'); -var nextSlot = 0; +let nextSlot = 0; Meteor._nodeCodeMustBeInFiber = function () { if (!Fiber.current) { throw new Error("Meteor code must always run within a Fiber. " + - "Try wrapping callbacks that you pass to non-Meteor " + - "libraries with Meteor.bindEnvironment."); + "Try wrapping callbacks that you pass to non-Meteor " + + "libraries with Meteor.bindEnvironment."); } }; -/** - * @memberOf Meteor - * @summary Constructor for EnvironmentVariable - * @locus Anywhere - * @class - */ -Meteor.EnvironmentVariable = function () { - this.slot = nextSlot++; -}; +class EnvironmentVariableFibers { + constructor() { + this.slot = nextSlot++; + } -var EVp = Meteor.EnvironmentVariable.prototype; - -/** - * @summary Return value of environment variable if available - * @locus Anywhere - * @method get - * @memberof Meteor.EnvironmentVariable - */ -EVp.get = function () { - if (Meteor._isFibersEnabled) { + get() { Meteor._nodeCodeMustBeInFiber(); return Fiber.current._meteor_dynamics && Fiber.current._meteor_dynamics[this.slot]; } - return Meteor._getValueFromAslStore("_meteor_dynamics")?.[this.slot] || null; -}; - -// Most Meteor code ought to run inside a fiber, and the -// _nodeCodeMustBeInFiber assertion helps you remember to include appropriate -// bindEnvironment calls (which will get you the *right value* for your -// environment variables, on the server). -// -// In some very special cases, it's more important to run Meteor code on the -// server in non-Fiber contexts rather than to strongly enforce the safeguard -// against forgetting to use bindEnvironment. For example, using `check` in -// some top-level constructs like connect handlers without needing unnecessary -// Fibers on every request is more important that possibly failing to find the -// correct argumentChecker. So this function is just like get(), but it -// returns null rather than throwing when called from outside a Fiber. (On the -// client, it is identical to get().) -EVp.getOrNullIfOutsideFiber = function () { - if (!Fiber.current || !Meteor._isFibersEnabled) - return null; - return this.get(); -}; - -function withValuesWithFiber(value, func) { - Meteor._nodeCodeMustBeInFiber(); - - if (!Fiber.current._meteor_dynamics) - Fiber.current._meteor_dynamics = []; - var currentValues = Fiber.current._meteor_dynamics; - - var saved = currentValues[this.slot]; - try { - currentValues[this.slot] = value; - return func(); - } finally { - currentValues[this.slot] = saved; - } -} -/** - * @summary Set the environment variable to the given value while a function is run - * @locus Anywhere - * @method withValue - * @memberof Meteor.EnvironmentVariable - * @param {Any} value Value the environment variable should be set to - * @param {Function} func The function to run - * @return {Any} Return value of function - */ -EVp.withValue = async function (value, func) { - if (Meteor._isFibersEnabled) { - return withValuesWithFiber.call(this, value, func); + getOrNullIfOutsideFiber() { + if (!Fiber.current) + return null; + return this.get(); } - let meteorDynamics = Meteor._getValueFromAslStore('_meteor_dynamics'); - if (!meteorDynamics) { - meteorDynamics = []; + withValue(value, func) { + Meteor._nodeCodeMustBeInFiber(); + + if (!Fiber.current._meteor_dynamics) + Fiber.current._meteor_dynamics = []; + var currentValues = Fiber.current._meteor_dynamics; + + var saved = currentValues[this.slot]; + try { + currentValues[this.slot] = value; + return func(); + } finally { + currentValues[this.slot] = saved; + } } - const saved = meteorDynamics[this.slot] || {}; - try { - meteorDynamics[this.slot] = value; - Meteor._updateAslStore('_meteor_dynamics', meteorDynamics); - return await func(); - } finally { - meteorDynamics[this.slot] = saved; - Meteor._updateAslStore('_meteor_dynamics', meteorDynamics); - } -}; - -/** - * @summary Set the environment variable to the given value while a function is run - * @locus Anywhere - * @method withValueAsync - * @memberof Meteor.EnvironmentVariable - * @param {Any} value Value the environment variable should be set to - * @param {Function} func The function to run - * @return {Any} Return value of function - */ -EVp._set = function (context) { - if (Meteor._isFibersEnabled) { + _set(context) { Meteor._nodeCodeMustBeInFiber(); Fiber.current._meteor_dynamics[this.slot] = context; - return; } - Meteor._updateAslStore("_meteor_dynamics", context); -}; - -EVp._setNewContextAndGetCurrent = function (value) { - if (Meteor._isFibersEnabled) { + _setNewContextAndGetCurrent(value) { Meteor._nodeCodeMustBeInFiber(); if (!Fiber.current._meteor_dynamics) { Fiber.current._meteor_dynamics = []; @@ -139,16 +60,64 @@ EVp._setNewContextAndGetCurrent = function (value) { this._set(value); return saved; } +} - let meteorDynamics = Meteor._getValueFromAslStore('_meteor_dynamics'); - if (!meteorDynamics) { - meteorDynamics = []; +class EnvironmentVariableAsync { + constructor() { + this.slot = nextSlot++; } - const saved = meteorDynamics[this.slot]; - this._set(value); - return saved; -}; + get() { + const currentValue = Meteor._getValueFromAslStore("_meteor_dynamics"); + return currentValue && currentValue[this.slot]; + } + + getOrNullIfOutsideFiber() { + return this.get(); + } + + async withValue(value, func) { + let currentValues = Meteor._getValueFromAslStore("_meteor_dynamics"); + if (!currentValues) { + currentValues = []; + } + + const saved = currentValues[this.slot]; + let ret; + try { + currentValues[this.slot] = value; + ret = await func(); + } finally { + currentValues[this.slot] = saved; + } + + return ret; + } + + _set(context) { + const _meteor_dynamics = Meteor._getValueFromAslStore("_meteor_dynamics") || []; + _meteor_dynamics[this.slot] = context; + } + + _setNewContextAndGetCurrent(value) { + let _meteor_dynamics = Meteor._getValueFromAslStore("_meteor_dynamics"); + if (!_meteor_dynamics) { + _meteor_dynamics = []; + } + + const saved = _meteor_dynamics[this.slot]; + this._set(value); + return saved; + } +} + +/** + * @memberOf Meteor + * @summary Constructor for EnvironmentVariable + * @locus Anywhere + * @class + */ +Meteor.EnvironmentVariable = Meteor._isFibersEnabled ? EnvironmentVariableFibers : EnvironmentVariableAsync; // Meteor application code is always supposed to be run inside a // fiber. bindEnvironment ensures that the function it wraps is run from @@ -181,97 +150,93 @@ EVp._setNewContextAndGetCurrent = function (value) { * @return {Function} The wrapped function */ Meteor.bindEnvironment = function (func, onException, _this) { - if (Meteor._isFibersEnabled) { - return bindEnvironmentWithFibers({ func, onException, _this }); - } - - const savedValues = Meteor._getValueFromAslStore('_meteor_dynamics'); - const boundValues = Array.isArray(savedValues) ? savedValues.slice() : []; - - return function(/* arguments */) { - const args = Array.prototype.slice.call(arguments); - - const runWithEnvironment = handleRunWithEnvironment({ - args, - _this, - func, - onException, - savedValues, - boundValues, - handleUpdate(values) { - Meteor._updateAslStore('_meteor_dynamics', values); - }, - }); - - runWithEnvironment(); - }; + return Meteor._isFibersEnabled ? bindEnvironmentFibers(func, onException, _this) : bindEnvironmentAsync(func, onException, _this); }; -const handleRunWithEnvironment = ({ - args, - _this, - savedValues, - boundValues, - handleUpdate, - onException, - func, -}) => () => { - let handleException = onException; - - if (!onException || typeof handleException === 'string') { - const description = handleException || 'callback of async function'; - handleException = function(error) { - Meteor._debug('Exception in ' + description + ':', error); - }; - } else if (typeof handleException !== 'function') { - throw new Error( - 'onException argument must be a function, string or undefined for Meteor.bindEnvironment().' - ); - } - - let ret; - try { - // Need to clone boundValues in case two fibers invoke this - // function at the same time - handleUpdate(boundValues.slice()); - - ret = func.apply(_this, args); - } catch (e) { - // TODO check the scenario from this comment when we don't have a Fiber - // note: callback-hook currently relies on the fact that if onException - // throws and you were originally calling the wrapped callback from - // within a Fiber, the wrapped call throws. - handleException(e); - } finally { - handleUpdate(savedValues); - } - return ret; -}; - -function bindEnvironmentWithFibers({ func, onException, _this }) { +const bindEnvironmentFibers = (func, onException, _this) => { Meteor._nodeCodeMustBeInFiber(); - // to keep the function scope and don't lose the Fiber.current reference - const fibersCurrent = Fiber.current; + var dynamics = Fiber.current._meteor_dynamics; + var boundValues = dynamics ? dynamics.slice() : []; - const savedValues = fibersCurrent._meteor_dynamics; - const boundValues = savedValues ? savedValues.slice() : []; + if (!onException || typeof(onException) === 'string') { + var description = onException || "callback of async function"; + onException = function (error) { + Meteor._debug( + "Exception in " + description + ":", + error + ); + }; + } else if (typeof(onException) !== 'function') { + throw new Error('onException argument must be a function, string or undefined for Meteor.bindEnvironment().'); + } - return function(/* arguments */) { - const args = Array.prototype.slice.call(arguments); - const runWithEnvironment = handleRunWithEnvironment({ - args, - _this, - func, - onException, - savedValues, - boundValues, - handleUpdate(values) { - fibersCurrent._meteor_dynamics = values; - }, - }); + return function (/* arguments */) { + var args = Array.prototype.slice.call(arguments); - if (Fiber.current) return runWithEnvironment(); + var runWithEnvironment = function () { + var savedValues = Fiber.current._meteor_dynamics; + try { + // Need to clone boundValues in case two fibers invoke this + // function at the same time + Fiber.current._meteor_dynamics = boundValues.slice(); + var ret = func.apply(_this, args); + } catch (e) { + // note: callback-hook currently relies on the fact that if onException + // throws and you were originally calling the wrapped callback from + // within a Fiber, the wrapped call throws. + onException(e); + } finally { + Fiber.current._meteor_dynamics = savedValues; + } + return ret; + }; + + if (Fiber.current) + return runWithEnvironment(); Fiber(runWithEnvironment).run(); }; -} +}; + +const bindEnvironmentAsync = (func, onException, _this) => { + var dynamics = Meteor._getValueFromAslStore("_meteor_dynamics"); + var boundValues = Array.isArray(dynamics) ? dynamics.slice() : []; + + if (!onException || typeof(onException) === 'string') { + var description = onException || "callback of async function"; + onException = function (error) { + Meteor._debug( + "Exception in " + description + ":", + error + ); + }; + } else if (typeof(onException) !== 'function') { + throw new Error('onException argument must be a function, string or undefined for Meteor.bindEnvironment().'); + } + + return function (/* arguments */) { + var args = Array.prototype.slice.call(arguments); + + var runWithEnvironment = async function () { + const savedValues = Meteor._getValueFromAslStore("_meteor_dynamics"); + let ret; + try { + // Need to clone boundValues in case two fibers invoke this + // function at the same time + // TODO -> Probably not needed + Meteor._updateAslStore("_meteor_dynamics", boundValues.slice()); + ret = await func.apply(_this, args); + } catch (e) { + onException(e); + } finally { + Meteor._updateAslStore("_meteor_dynamics", savedValues); + } + return ret; + }; + + if (Meteor._getAslStore()) { + return runWithEnvironment(); + } + global.asyncLocalStorage.run({}, runWithEnvironment); + }; +};