mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Organizing code.
This commit is contained in:
@@ -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);
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user