From a0b4c9ab034dc3baff52b53c3b9c821f0114df3b Mon Sep 17 00:00:00 2001 From: zodern Date: Mon, 21 Oct 2024 19:02:59 -0500 Subject: [PATCH 1/9] Simplify dynamics on nodejs --- packages/meteor/dynamics_nodejs.js | 108 ++++++++++------------------- 1 file changed, 35 insertions(+), 73 deletions(-) diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index 1e3f2b287a..aa83ae548b 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -2,10 +2,6 @@ let nextSlot = 0; let callAsyncMethodRunning = false; -const CURRENT_VALUE_KEY_NAME = "currentValue"; -const UPPER_CALL_DYNAMICS_KEY_NAME = "upperCallDynamics"; - -const SLOT_CALL_KEY = "slotCall"; /** * @memberOf Meteor * @summary Constructor for EnvironmentVariable @@ -26,12 +22,11 @@ class EnvironmentVariableAsync { * @returns {any} The current value of the variable, or `undefined` if no */ get() { - if (this.slot !== Meteor._getValueFromAslStore(SLOT_CALL_KEY)) { - const dynamics = Meteor._getValueFromAslStore(UPPER_CALL_DYNAMICS_KEY_NAME) || {}; + let store = Meteor._getAslStore(); - return dynamics[this.slot]; + if (store && store.dynamics) { + return store.dynamics[this.slot]; } - return Meteor._getValueFromAslStore(CURRENT_VALUE_KEY_NAME); } getOrNullIfOutsideFiber() { @@ -48,48 +43,31 @@ class EnvironmentVariableAsync { * @returns {Promise} The return value of the function */ withValue(value, func, options = {}) { - const self = this; - const slotCall = self.slot; - const dynamics = Object.assign( - {}, - Meteor._getValueFromAslStore(UPPER_CALL_DYNAMICS_KEY_NAME) || {} - ); + let store = Meteor._getAslStore(); + let dynamics = store && store.dynamics ? store.dynamics.slice() : []; + dynamics[this.slot] = value; - if (slotCall != null) { - dynamics[slotCall] = value; + let newStore = { dynamics: dynamics }; + + if (options) { + Object.assign(newStore, options); } - return Meteor._runAsync( - function () { - Meteor._updateAslStore(CURRENT_VALUE_KEY_NAME, value); - Meteor._updateAslStore(UPPER_CALL_DYNAMICS_KEY_NAME, dynamics); - return func(); - }, - self, - Object.assign( - { - callId: `${this.slot}-${Math.random()}`, - [SLOT_CALL_KEY]: this.slot, - }, - options, - ), - ); + return Meteor._getAsl().run(newStore, func); } - _set(context) { - const _meteor_dynamics = - Meteor._getValueFromAslStore("_meteor_dynamics") || []; - _meteor_dynamics[this.slot] = context; + _set(value) { + const dynamics = Meteor._getValueFromAslStore('dynamics') || []; + dynamics[this.slot] = value; } _setNewContextAndGetCurrent(value) { - let _meteor_dynamics = Meteor._getValueFromAslStore("_meteor_dynamics"); - if (!_meteor_dynamics) { - _meteor_dynamics = []; - } + const dynamics = Meteor._getValueFromAslStore('dynamics') || []; + + const saved = dynamics[this.slot]; + + dynamics[this.slot] = value; - const saved = _meteor_dynamics[this.slot]; - this._set(value); return saved; } @@ -145,8 +123,8 @@ Meteor.EnvironmentVariable = EnvironmentVariableAsync; * @return {Function} The wrapped function */ Meteor.bindEnvironment = (func, onException, _this) => { - const dynamics = Meteor._getValueFromAslStore(CURRENT_VALUE_KEY_NAME); - const currentSlot = Meteor._getValueFromAslStore(SLOT_CALL_KEY); + let store = Meteor._getAsl().getStore(); + let dynamics = store && store.dynamics ? store.dynamics.slice() : []; if (!onException || typeof onException === "string") { var description = onException || "callback of async function"; @@ -162,37 +140,21 @@ Meteor.bindEnvironment = (func, onException, _this) => { return function (/* arguments */) { var args = Array.prototype.slice.call(arguments); - var runWithEnvironment = function () { - return Meteor._runAsync( - () => { - let ret; - try { - if (currentSlot) { - Meteor._updateAslStore(CURRENT_VALUE_KEY_NAME, dynamics); - } - ret = func.apply(_this, args); + return Meteor._getAsl().run({ + dynamics: dynamics + }, function () { + let ret; + try { + ret = func.apply(_this, args); - // Using this strategy to be consistent between client and server and stop always returning a promise from the server - if (Meteor._isPromise(ret)) { - ret = ret.catch(onException); - } - } catch (e) { - onException(e); - } - return ret; - }, - _this, - { - callId: `bindEnvironment-${Math.random()}`, - [SLOT_CALL_KEY]: currentSlot, + // Using this strategy to be consistent between client and server and stop always returning a promise from the server + if (Meteor._isPromise(ret)) { + ret = ret.catch(onException); } - ); - }; - - if (Meteor._getAslStore()) { - return runWithEnvironment(); - } - - return Meteor._getAsl().run({}, runWithEnvironment); + } catch (e) { + onException(e); + } + return ret; + }); }; }; From 6065c2d23f37d5bcafa017b854e21bb730b19c71 Mon Sep 17 00:00:00 2001 From: Leonardo Venturini Date: Wed, 23 Oct 2024 07:54:47 -0400 Subject: [PATCH 2/9] cleanup collections before starting tests --- packages/tinytest/tinytest_server.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/tinytest/tinytest_server.js b/packages/tinytest/tinytest_server.js index fa51723879..22a781a4c5 100644 --- a/packages/tinytest/tinytest_server.js +++ b/packages/tinytest/tinytest_server.js @@ -12,7 +12,7 @@ export { Tinytest }; const handlesForRun = new Map; const reportsForRun = new Map; -Meteor.publish(ServerTestResultsSubscription, function (runId) { +Meteor.publish(ServerTestResultsSubscription, async function (runId) { check(runId, String); if (! handlesForRun.has(runId)) { @@ -36,9 +36,16 @@ Meteor.publish(ServerTestResultsSubscription, function (runId) { }); Meteor.methods({ - 'tinytest/run'(runId, pathPrefix) { + async 'tinytest/run'(runId, pathPrefix) { check(runId, String); check(pathPrefix, Match.Optional([String])); + + const collections = await MongoInternals.defaultRemoteCollectionDriver().mongo.db.collections(); + + for (const collection of collections) { + await collection.drop(); + } + this.unblock(); reportsForRun.set(runId, Object.create(null)); @@ -84,4 +91,4 @@ Meteor.methods({ handlesForRun.delete(runId); reportsForRun.delete(runId); } -}); +}); \ No newline at end of file From db5041fb6823c0b83028c08193309fc2c65ac2fa Mon Sep 17 00:00:00 2001 From: Leonardo Venturini Date: Wed, 23 Oct 2024 09:01:52 -0400 Subject: [PATCH 3/9] fix data population --- .../ddp-client/test/livedata_test_service.js | 63 ++++++++++--------- packages/tinytest/tinytest_server.js | 2 +- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/packages/ddp-client/test/livedata_test_service.js b/packages/ddp-client/test/livedata_test_service.js index bc6e31b2e1..56886ff384 100644 --- a/packages/ddp-client/test/livedata_test_service.js +++ b/packages/ddp-client/test/livedata_test_service.js @@ -334,38 +334,41 @@ if (Meteor.isServer) { One = new Mongo.Collection('collectionOne'); Two = new Mongo.Collection('collectionTwo'); -Meteor.startup(async () => { - if (Meteor.isServer) { - await One.removeAsync({}); - await One.insertAsync({ name: 'value1' }); - await One.insertAsync({ name: 'value2' }); +async function populateDatabase() { + await One.removeAsync({}); + await One.insertAsync({ name: 'value1' }); + await One.insertAsync({ name: 'value2' }); - await Two.removeAsync({}); - await Two.insertAsync({ name: 'value3' }); - await Two.insertAsync({ name: 'value4' }); - await Two.insertAsync({ name: 'value5' }); + await Two.removeAsync({}); + await Two.insertAsync({ name: 'value3' }); + await Two.insertAsync({ name: 'value4' }); + await Two.insertAsync({ name: 'value5' }); +} - Meteor.publish('multiPublish', function(options) { - // See below to see what options are accepted. - check(options, Object); - if (options.normal) { - return [One.find(), Two.find()]; - } else if (options.dup) { - // Suppress the log of the expected internal error. - Meteor._suppress_log(1); - return [ - One.find(), - One.find({ name: 'value2' }), // multiple cursors for one collection - error - Two.find(), - ]; - } else if (options.notCursor) { - // Suppress the log of the expected internal error. - Meteor._suppress_log(1); - return [One.find(), 'not a cursor', Two.find()]; - } else throw 'unexpected options'; - }); - } -}); +if (Meteor.isServer) { + Meteor.publish('multiPublish', async function (options) { + // See below to see what options are accepted. + check(options, Object); + + await populateDatabase(); + + if (options.normal) { + return [One.find(), Two.find()]; + } else if (options.dup) { + // Suppress the log of the expected internal error. + Meteor._suppress_log(1); + return [ + One.find(), + One.find({ name: 'value2' }), // multiple cursors for one collection - error + Two.find(), + ]; + } else if (options.notCursor) { + // Suppress the log of the expected internal error. + Meteor._suppress_log(1); + return [One.find(), 'not a cursor', Two.find()]; + } else throw 'unexpected options'; + }); +} /// Helper for "livedata - result by value" const resultByValueArrays = Object.create(null); diff --git a/packages/tinytest/tinytest_server.js b/packages/tinytest/tinytest_server.js index 22a781a4c5..4c514c1059 100644 --- a/packages/tinytest/tinytest_server.js +++ b/packages/tinytest/tinytest_server.js @@ -43,7 +43,7 @@ Meteor.methods({ const collections = await MongoInternals.defaultRemoteCollectionDriver().mongo.db.collections(); for (const collection of collections) { - await collection.drop(); + await collection.deleteMany({}); } this.unblock(); From d9692c8b5eba0ab540efff8cce804609a0571826 Mon Sep 17 00:00:00 2001 From: Leonardo Venturini Date: Wed, 23 Oct 2024 09:42:37 -0400 Subject: [PATCH 4/9] prevent context from tinytest to trickle down to actual test --- packages/tinytest/tinytest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tinytest/tinytest.js b/packages/tinytest/tinytest.js index 73be02c9f8..afa1391207 100644 --- a/packages/tinytest/tinytest.js +++ b/packages/tinytest/tinytest.js @@ -435,7 +435,7 @@ export class TestCase { stop_at_offset ); - const result = this.func(results, resolve); + const result = Meteor._runFresh(() => this.func(results, resolve)); if (result && typeof result.then === "function") { return result.then(resolve, reject); } From 401940d65229a21d57c24652e3b08b6c25650f9b Mon Sep 17 00:00:00 2001 From: Leonardo Venturini Date: Thu, 24 Oct 2024 07:56:06 -0400 Subject: [PATCH 5/9] add test --- packages/meteor/dynamics_test.js | 47 ++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/packages/meteor/dynamics_test.js b/packages/meteor/dynamics_test.js index e33a28ad97..2c33177705 100644 --- a/packages/meteor/dynamics_test.js +++ b/packages/meteor/dynamics_test.js @@ -183,6 +183,53 @@ Tinytest.addAsync("environment - bare bindEnvironment", * This won't work on the client due to the absence of ALS/AH */ if (Meteor.isServer) { + Tinytest.addAsync("environment - defer and environment variables", async function (test) { + const varA = new Meteor.EnvironmentVariable("a"); + const varB = new Meteor.EnvironmentVariable("b"); + + let deferOnly = null; + + varA.withValue(1, () => { + varB.withValue(2, () => { + Meteor.defer(() => { + console.log('Defer', varA.get(), varB.get()); + + deferOnly = [varA.get(), varB.get()]; + }); + }); + }); + + let deferWithBindEnv = null; + + varA.withValue(1, () => { + varB.withValue(2, () => { + Meteor.defer( + Meteor.bindEnvironment(() => { + console.log('Defer + Bind', varA.get(), varB.get()); + + deferWithBindEnv = [varA.get(), varB.get()]; + }) + ); + }); + }); + + let raw = null; + + varA.withValue(1, () => { + varB.withValue(2, () => { + console.log('Raw:', varA.get(), varB.get()); + + raw = [varA.get(), varB.get()]; + }); + }); + + await Meteor.sleep(100); + + test.equal(deferOnly, [1, 2]); + test.equal(deferWithBindEnv, [1, 2]); + test.equal(raw, [1, 2]); + }) + Tinytest.addAsync('environment - preserve ev value async/await', async function (test) { let val1 = null; let val2 = null; From 5056521adf2aeefdf8f430eb1839ed634df5419b Mon Sep 17 00:00:00 2001 From: Leonardo Venturini Date: Thu, 24 Oct 2024 07:56:20 -0400 Subject: [PATCH 6/9] shrinkwrap --- packages/accounts-2fa/.npm/package/npm-shrinkwrap.json | 6 +++--- packages/email/.npm/package/npm-shrinkwrap.json | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/accounts-2fa/.npm/package/npm-shrinkwrap.json b/packages/accounts-2fa/.npm/package/npm-shrinkwrap.json index 64d0411523..231870d16f 100644 --- a/packages/accounts-2fa/.npm/package/npm-shrinkwrap.json +++ b/packages/accounts-2fa/.npm/package/npm-shrinkwrap.json @@ -2,9 +2,9 @@ "lockfileVersion": 4, "dependencies": { "@types/node": { - "version": "22.7.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.6.tgz", - "integrity": "sha512-/d7Rnj0/ExXDMcioS78/kf1lMzYk4BZV8MZGTBKzTGZ6/406ukkbYlIsZmMPhcR5KlkunDHQLrtAVmSq7r+mSw==" + "version": "22.7.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.9.tgz", + "integrity": "sha512-jrTfRC7FM6nChvU7X2KqcrgquofrWLFDeYC1hKfwNWomVvrn7JIksqf344WN2X/y8xrgqBd2dJATZV4GbatBfg==" }, "@types/notp": { "version": "2.0.5", diff --git a/packages/email/.npm/package/npm-shrinkwrap.json b/packages/email/.npm/package/npm-shrinkwrap.json index 68404a03da..5aecda5383 100644 --- a/packages/email/.npm/package/npm-shrinkwrap.json +++ b/packages/email/.npm/package/npm-shrinkwrap.json @@ -2,9 +2,9 @@ "lockfileVersion": 4, "dependencies": { "@types/node": { - "version": "22.7.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.6.tgz", - "integrity": "sha512-/d7Rnj0/ExXDMcioS78/kf1lMzYk4BZV8MZGTBKzTGZ6/406ukkbYlIsZmMPhcR5KlkunDHQLrtAVmSq7r+mSw==" + "version": "22.7.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.9.tgz", + "integrity": "sha512-jrTfRC7FM6nChvU7X2KqcrgquofrWLFDeYC1hKfwNWomVvrn7JIksqf344WN2X/y8xrgqBd2dJATZV4GbatBfg==" }, "@types/nodemailer": { "version": "6.4.14", From 986ffcc789d9af4c2f688fe8bdf92350ba33eedd Mon Sep 17 00:00:00 2001 From: Leonardo Venturini Date: Thu, 24 Oct 2024 08:06:18 -0400 Subject: [PATCH 7/9] fix exception crashing test server --- packages/email/email_tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/email/email_tests.js b/packages/email/email_tests.js index 05cfde3414..1decde3438 100644 --- a/packages/email/email_tests.js +++ b/packages/email/email_tests.js @@ -18,7 +18,7 @@ TEST_CASES.forEach(({ title, options, testCalls }) => { testCall(test, stream); }); }); - Promise.all(allPromises).then(() => resolver()); + Promise.all(allPromises).then(() => resolver()).catch(console.error); }); await promise; }); From f969ab9bb4d23a78b574df06be3ce8baae714afe Mon Sep 17 00:00:00 2001 From: Leonardo Venturini Date: Fri, 25 Oct 2024 09:44:46 -0400 Subject: [PATCH 8/9] fix unsetting method invocation --- packages/ddp-server/livedata_server.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ddp-server/livedata_server.js b/packages/ddp-server/livedata_server.js index a79d0febf2..cd7a24094c 100644 --- a/packages/ddp-server/livedata_server.js +++ b/packages/ddp-server/livedata_server.js @@ -1794,7 +1794,6 @@ Object.assign(Server.prototype, { const options = args[0]?.hasOwnProperty('returnStubValue') ? args.shift() : {}; - DDP._CurrentMethodInvocation._set(); DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(true); const promise = new Promise((resolve, reject) => { DDP._CurrentCallAsyncInvocation._set({ name, hasCallAsyncParent: true }); From b0056152898ee0a606d39c4e7e40453a3e9178d3 Mon Sep 17 00:00:00 2001 From: Leonardo Venturini Date: Fri, 25 Oct 2024 09:55:07 -0400 Subject: [PATCH 9/9] add sleep --- packages/meteor/async_helpers.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/meteor/async_helpers.js b/packages/meteor/async_helpers.js index fc1175d9c0..4d7a9be8ba 100644 --- a/packages/meteor/async_helpers.js +++ b/packages/meteor/async_helpers.js @@ -171,3 +171,5 @@ const _sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); Meteor._sleepForMs = function (ms) { return _sleep(ms); }; + +Meteor.sleep = _sleep; \ No newline at end of file