Tracker flush cycle should yield occasionally if not invoked via Tracker.flush #3901
Login by creating new user. Hit Import button to get infinite loop.
Note that this problem was very difficult to narrow down, as system simply hangs.
(I whittled away plenty of code to reproduce, and probably could whittle away some more.)
The text was updated successfully, but these errors were encountered:
Hi. Just to make sure I understand properly: you're saying that when you make your app be smaller (fewer than 3 subscriptions, 4 methods, 2 transforms, the use of accounts-ui and accounts-password, etc), the bug goes away? So this is some sort of bug that relies precisely on the combination of accounts-password, accounts-ui, transforms, and having three separate subscriptions?
Thanks, that's a lot more clear.
In each run, you create a brand new Uhoh. You then call isStarted on it, which looks up the "isStarted" key in the ReactiveDict. Tracker notices this and decides that it will rerun when the isStarted key in the ReactiveDict changes. You then immediately go and change the "isStarted" key, which leads Tracker to decide that the computation needs to be rerun at the next flush cycle.
So after rendering the page, Tracker re-runs the computation. But since you have a brand new Uhoh this time through, everything said above happens again. And again and again.
If you remove the line that sets isStarted to false (and eg, just default to returning false without updating the reactiveDict), everything works fine.
So, the fact that it keeps recomputing isn't a bug. It's working as intended.
On the other hand, it would be nice if the behavior here wasn't so catastrophic: if this didn't turn into an infinite loop in Tracker.flush.
There are three ways I can think of to avoid this.
(2) The other would be to fundamentally change the API of Tracker.flush so that instead of returning when flush is done, it returns at some arbitrary point, and if you want to be notified when the flush is done, use Tracker.afterFlush. Then, after having run some large number of computations, it can yield to the event loop for a bit. However, this is a backwards-incompatible change!
(3) What I'd really suggest is the following.
Tracker.flush() will still guarantee that all computations and afterFlush callbacks have been fully flushed, but the implicit flush cycle started by an invalidation or afterFlush call will yield to the event loop for 10ms every thousand computations. Fixes #3901.
I just tried this in METEOR@1.0.5, and the problem is still there: the system hangs.
I also disagree with the classification (in https://rbcommons.com/s/meteor/r/25/) of this coding style as a 'bug'. Granted that in the watered down example I presented it is silly to code that way, but in deeply nested code with reusable components I see this as a very valid scenario.