Organizing code.

This commit is contained in:
Matheus Castro
2022-10-21 00:09:55 -03:00
parent 53ff57b492
commit 61a0dbbb08

View File

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