Error: Can't set timers inside simulations?

I sometimes get this error on the front end. However I am not setting timeouts anywhere. I am using the new async methods inside Meteor.methods.

Basically the only method I have is this (on both sides, but I’ve learned that Meteor.methods are no longer good for simulations and isomorphic code like old code used to be before non-async methods were removed):

Meteor.methods({
	async 'visits.increment'(route: string) {
		console.log('VISITS INCREMENT')
		await Visits.upsertAsync({route}, {$inc: {visits: 1}})
		console.log('UPDATED:', (await Visits.findOneAsync({route})).visits)
	},
})

This is slightly related to

In the past, we were able to write isomorphic methods very easily. Now we are not, and the problem I have is probably because I should no longer have the method on the client-side (no longer isomorphic).

but I’ve learned that Meteor.methods are no longer good for simulations and isomorphic code

You can write isomorphic code with the *Async suffix inside Meteor.methods.

Hmm I can’t reproduce the error you’re seeing based on your code sample above. What am I missing? What version of Meteor are you using?

Yes, but not isomorphic reactive code. That’s been removed, which makes the development experience not as good as before: reactivity is the magic that Meteor gave us, and part of that magic is now replaced with isomorphic promise-based non-reactive code.

I’m on Meteor 3.1.2 with latest packages as of time of writing.

I also cannot reproduce all the time, it happens sporadically.

Hmm, I’m not following. Can you give a simple example of what is no longer possible?

If your method does not set timers, and you do not see the warning Method stub (<method name>) took too long and could cause unexpected problems., this should be impossible. If you are still seeing this error, it is a little concerning.

One thing you could try is setting a breakpoint on the line that throws the error. When you next see the error, it would be helpful to know what the stack trace is. Specifically, why is the code running - was some callback or event run during a method simulation, or is it actually related somehow to the method?

This is my hunch if this is happening sporadically and difficult to replicate.

1 Like

Sure:

In the past we could write this:

Meteor.methods({
  someMethod() {
    console.log('books count:', Books.find({}).count())
    console.log('authors count:', Authors.find({}).count())
  }
})

and we could use this method on both the server and the client isomorphically. But! Additionally, we were also able to do the following on the client, and it would trigger reactivity as expected:

Tracker.autorun(() => {
  // any time the `Books` collection is updated, this
  // will re-run the autorun, logging the number of books,
  // and the number of authors
  Meteor.call('someMethod')
})

Now, because Meteor recently removed the synchronous collection APIs from backend, methods have to be written like so:

Meteor.methods({
  async someMethod() {
    console.log('books count:', await Books.find({}).countAsync())
    console.log('authors count:', await Authors.find({}).countAsync())
  }
})

Although the new method using await is still isomorphic meaning it can be called on both server and client, it is no longer reactively isomorphic: if you try to write the same Tracker.autorun as the previous sample,

Tracker.autorun(() => {
  Meteor.call('someMethod')
})

it no longer works as expected! If the number of authors changes, but the number of books does not, the autorun will not rerun because after the first await call, the effect (the computation in Meteor terms) is no longer able to track all the dependencies after the await call.

This means that isomorphism is still in place but reactivity is not.

To work around this, one has two options:

1) use Tracker.withComputation and make the code a little messier but also may introduce reactive race conditions because effects can now be interleaved with each other in a way that isn’t as easy to understand as synchronous effects.

2) or write separate methods for server vs client (no longer isomorphic). For example:

if (Meteor.isClient) {
  Meteor.methods({
    someMethodClient() {
      console.log('books count:', Books.find({}).count())
      console.log('authors count:', Authors.find({}).count())
    }
  })
} else {
  Meteor.methods({
    async someMethodServer() {
      console.log('books count:', await Books.find({}).countAsync())
      console.log('authors count:', await Authors.find({}).countAsync())
    }
  })
}

Note, we could name them the someMethod on both sides, but it might be unexpected and confusing to have different behavior on one side vs the other.

The ideal would be to have a single way of defining methods on server and client, and for it to work the same way on both sides (reactively, without Fibers). To learn how this is possible, recommend studying Solid.js and its current createResource API. The createResource API creates a signal that is connected to an async process (could be network requests for example) that can then be used in synchronous effects functions. There are other patterns too like what I described above, and Solid.js is also coming out with a new createAsync API in Solid 2.0 as alternative to createResource.

It is possible we can start to build similar primitives on top of Meteor’s signals and effects (Tracker+ReactiveVar) to achieve similar patterns, and use them as an alternative to Meteor.methods. First we’d have to ensure we update Tracker and related APIs to be server-compatible (the docs currently mention they are client-only), that way we can write the same reactive code on both sides.

Yep, that’s what I’m seeing (not the error you mentioned, only the one I wrote above).

Maybe I was calling the method when it was still awaiting? I refactored the code already and haven’t had the issue since (moving fast). I’ll aim for a reproduction if it happens again.

Thanks! I honestly had no idea that was a thing previously. I assumed people used pub-sub if they wanted to read reactive updates to a collection(s).

Seems kind of like a potential foot gun but maybe I’m missing something. Would be curious to hear of real world scenarios where people are / were using this pattern and why it’s preferred over pub-sub:

Tracker.autorun(() => {
  Meteor.call('someMethod')
})

These are tangential. Pub-sub is still required for exposing the data to the client, but the method would separately (before the Promise-based APIs) be reactive on the client (if the data is present due to the pubsub).

Now, after the APIs have been changed to promises, pubsub still does the same thing as before and exposes the data to the client, but the difference in how the methods works changed as described above regardless of the existence of pubsub.

Pubsub pushes server data into collection, and then separately Meteor methods can read that data, and this change only affects the latter (pubsub being required either way).

Hopefully that cleared up the question. The two things, pubsub and meteor methods, are two separate things, and only the latter has been affected but the way you use pubsub still remains the same and does not affect how you use methods.

Another way to put it is, without pubsub, both pre-promise Meteor, and post-promise Meteor, would have no way to expose data to methods running on the client (doesn’t matter if the methods are async or not, pubsub is required either way (without the autopublish package for prototyping)).

Would a workaround be to make a synchronous query in the autorun callback on the relevant collection and use that to trigger the necessary Meteor method call?

Not as convenient mind you. But maybe something that’s a low-cost op on the client would be good enough.

(I haven’t tested this and I’m embarrassed to say I haven’t been working much with pub sub recently!)