Skip to content

[Glimmer 2] "Backtracking re-render" is now an assertion #13948

@chancancode

Description

@chancancode

Backtracking re-render refers to a scenario where, in the middle of the rendering process, you have modified something that has already been rendered.

For example:

{{foo}} {{foo-bar parent=this}}
// app/components/foo-bar.js

export default Ember.Component.extend({
  init() {
    this._super(...arguments);
    this.get('parent').set('foo', 'bar');
  }  
});

As you can see, by the time the foo-bar component is instantiated and rendered, we have already used the value foo from the parent context to populate the {{foo}} curly. However, in its constructor, it was trying to modify the same value, hence. "backtracking".

This is a pretty extreme example, but it illustrates the problem. Besides init, didInitAttrs, didReceiveAttrs, willInsertElement and willRender also happens synchronously during the rendering process. Additionally, backtracking is often an issue arising from the behavior of two-way bound properties.

This behavior has always been unreliable, but was partially supported with a deprecation (since Ember 1.13):

You modified ApplicationController.foo twice in a single render. This was unreliable in Ember 1.x and will be removed in Ember 3.0

Since 1.13, Ember supported this by immediately doing a second re-render when backtracking was detected (and then repeating until the system stabilizes). This strategy itself could be a source of performance problems. In extreme cases, this could cause an infinite loop.

In Glimmer 2, while the extra re-render is relatively cheap, the extra book-keeping to detect a backtracking set is not. One of the wins of the Glimmer 2 system is that it does not need to eagerly setup observers to track changes. Further, certain optimizations in Glimmer 2 allows the system to skip traversing subtrees when it knows nothing within it has changed.

Together, these factors mean that we can not readily detect these backtracking sets (or whether something was "already rendered" or not) without doing a large amount of extra book-keeping and intentionally defeating these optimizations.

We already wrote the code to support this, but due to the already unreliable nature of the feature, and the (very significant) book-keeping costs, we are hesitant to automatically enable them for everyone without knowing whether it is still needed.

As a compromise, we currently only perform the detection in development mode and turned the deprecation message into a development-mode assertion (hard error). In production mode, the detection code is stripped and backtracking will not work.

We have kept the facility to support this feature (without the assertion) in the codebase behind a second feature flag. The code is being tested continuously on CI, however, is disabled by default until we have enough usage information to determine next steps.

If you believe you have usage patterns that are affected by this, please provide as much detail about your scenario as possible below. It is very possible that there can be alternatives and/or targeted solutions we can use that do not require a wholesale change to the engine. Therefore, it would be helpful if you provide background information and context about your usage instead of just showing us small code snippets from your codebase.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions