Problem with EnvironmentVariable

In our applications, we have been using Meteor.EnvironmentVariable to handle cross-function calls. I find a discrepancy between how 2.14 and earlier work and 3.0. Here is a simple method that simulates the problem:

const linkContext = new Meteor.EnvironmentVariable();

Meteor.methods({
    async test() {
        console.log('Method invocation user', Meteor.userId());
        linkContext.withValue({ idd: Random.id() }, async () => {
            console.log('Link context', linkContext.get())
            console.log('Method invocation user inner', Meteor.userId());
        });
        return 'ok';
    }
})

In 2.14 I get as output

Method call user 5na9a9daGL3nf7KS8
Link context { idd: 'QERNEZZXqBvH2nH8X' }
Method call user inner 5na9a9daGL3nf7KS8

Whereas in 3.0 I get the error:

Method invocation user 5na9a9daGL3nf7KS8
Link context { idd: 'kwN57Kqi6uTEMGtLD' }
meteor://💻app/packages/accounts-base.js:762
 if (!currentInvocation) throw new Error("Meteor.userId can only be invoked in method calls or publications.");

Error: Meteor.userId can only be invoked in method calls or publications.
     at AccountsServer.userId (packages/accounts-base/accounts_server.js:124:13)
     at Object.Meteor.userId (packages/accounts-base/accounts_common.js:425:32)
     at server/main.js:32:64
     at EnvironmentVariableAsync.<anonymous> (packages/meteor.js:1241:23)
     at packages/meteor.js:740:19
     at AsyncLocalStorage.run (node:async_hooks:346:14)
     at Object.Meteor._runAsync (packages/meteor.js:737:37)
     at EnvironmentVariableAsync.withValue (packages/meteor.js:1236:19)
     at MethodInvocation.test (server/main.js:30:21)
     at maybeAuditArgumentChecks (packages/ddp-server/livedata_server.js:1990:12)
     at packages/ddp-server/livedata_server.js:1899:11
     at EnvironmentVariableAsync.<anonymous> (packages/meteor.js:1241:23)
     at packages/meteor.js:740:19
     at AsyncLocalStorage.run (node:async_hooks:346:14)
     at Object.Meteor._runAsync (packages/meteor.js:737:37)
     at EnvironmentVariableAsync.withValue (packages/meteor.js:1236:19)

From a rough reconstruction, it seems that the local EnvironmentVariable overwrites the _CurrentMethodInvocation. Is there an alternative way to do this in the framework?

Can this be related to the loss of context described here: Can't we do Meteor.userAsync() in pubs anymore? - #3 by bratelefant ?!

1 Like

I’d definitely say yes, due to the fact that the I can confirm exact behavior with my own server function calls consuming Meteor.userId on the server from within publication and also this is what breaks meteor-collection-hooks for me on 3.0

It doesn’t seem related but I could be wrong. In my case it seems it is not possible to have multiple contexts at the same time.

Hello @denyhs, @grubba. Is there any confirmation or denial of the reported bug? Are any changes planned to make the EnvironmentVariable work the same in 3.0 as it did in 2.x?

Thank you very much.

Hi @pbeato, to confirm, are you testing this in beta-0?

I’ll try to reproduce this later to understand what’s happening.

Hi @denyhs, yes, I tried it in version 3.0-beta.0.

Hi all, same issue here with Meteor.userId on publications server side. Thanks for your work

Hi @denyhs is there any news about this? Thanks

I think this was fixed in the last Meteor 3.0 beta, right @denyhs?

Hi, we’ve released beta.4, which should be fixed now.

@pbeato, can you please test it again?

Hi @denyhs, I tried but I have another problem. It seems that nested calls are not handled correctly, below is a simple example:

import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';

const linkContext1 = new Meteor.EnvironmentVariable();
const linkContext2 = new Meteor.EnvironmentVariable();

linkContext1.withValue({ idd: Random.id() }, async () => {
    await linkContext2.withValue({ idd: Random.id() }, async () => {
        await linkContext2.withValue({ idd: Random.id() }, async () => {
            console.log('    Link context 2', linkContext2.get())
        })
        console.log('  Link context 2', linkContext2.get())
    })
    console.log('Link context 1', linkContext1.get())
    console.log('Link context 2', linkContext2.get())
});

The result is:

     Link context 2 { idd: 'nai9jtXk7mAdrQq5N' }
   Link context 2 { idd: 'Di6aKku65s2pdne5L' }
 Link context 1 { idd: 'E97X6ZwjM6cEjbxpN' }
 **Link context 2 { idd: 'Di6aKku65s2pdne5L' }  // Should be undefined but we have the last id**

As a curiosity, do you see any contraindications in using AsyncLocalStorage directly for some properties without going through Meteor.EnvironmentVariable?

1 Like

A reply would be appreciated, at least to understand whether we want to tackle the thing or we have to think of a custom workaround. Thank you.

1 Like

Hey! I’ll check if @denyhs can make us a card for that. If I understood correctly, is the value not cleaned at the end?

For now, while we are working on this, maybe we could think of a workaround, I believe this snippet should do the trick(have not tested it but I’m almost sure that it works):


const withContext = async (ctx, value, fn) => {
  await ctx.withValue(value, async () => {
    await fn();
    // this example I only clean objects
   if( typeof value === 'object' && value !== null){
        for (const member in value) delete value[member];
    } 

  });
}

Hey @pbeato sorry for the delay, and thank you! I’m going to recheck this soon.

1 Like