Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(engine): refactor boundary protection for render phase #1507

Merged

Conversation

ravijayaramappa
Copy link
Contributor

@ravijayaramappa ravijayaramappa commented Sep 13, 2019

Details

  1. User land code is protected from making dangerous mutation during rendering phase.
  2. Rendering a component is two steps, first the render() is invoked and then the returned template is evaluated.
  3. The userland code was being protected using one global flag called isRendering.
  4. In between these two steps, the engine is executing its own routine and does not need to be protected from dangerous mutations.

This PR separated the flag into two. One for render invocation and another for template evaluation.

Addresses issue #1506

Does this PR introduce breaking changes?

  • No, it does not introduce breaking changes.

If yes, please describe the impact and migration path for existing applications.

The PR fulfills these requirements:

  • Have tests for the proposed changes been added? ✅
  • Have you followed these instructions to clearly describe the issue being fixed or feature enhanced? ✅

test case: A parent switches template and a child being disconnected sets a tracked property in  disconnectedCallback
@ravijayaramappa ravijayaramappa changed the title fix(engine): separate global flag for render invocation and template evaluation fix[engine]: separate global flag for render invocation and template evaluation Sep 13, 2019
@ravijayaramappa ravijayaramappa changed the title fix[engine]: separate global flag for render invocation and template evaluation fix[engine]: refactor boundary protection for render phase Sep 13, 2019
@ravijayaramappa ravijayaramappa changed the title fix[engine]: refactor boundary protection for render phase fix(engine): refactor boundary protection for render phase Sep 13, 2019
}
const isEvaluatingTemplateInception = isEvaluatingTemplate;
let vnodes: VNodes = [];
runWithBoundaryProtection(

This comment was marked as outdated.

This comment was marked as resolved.

@@ -124,6 +124,7 @@ export function invokeComponentRenderMethod(vm: VM): VNodes {
() => {
// job
const html = callHook(component, render);
isRendering = false;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@caridy Should we call this flag isRenderBeingInvoked or something like that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, feel free to rename it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line can't be done here... because if the render() method throws, the state is never restored! also inception is not preserved when you assign the false...

() => {
// invoke the selected template.
vnodes = html.call(undefined, api, component, cmpSlots, context.tplCache!);
const { styleVNode } = context;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this block should be outside of the protection.

}

if (process.env.NODE_ENV !== 'production') {
assert.invariant(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imagine that this throws an error... that will be captured by the boundary... that's not what we want.

Copy link
Contributor

@caridy caridy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still not entirely convinced that this is safe.

@@ -609,10 +615,10 @@ export function ll(
id: string,
context?: (...args: any[]) => any
): EventListener {
const vmBeingRendered: VM | null = getVMBeingRendered();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should call this vm as it was before, because it is using in the next turn where vmBeingRendered is not longer set, it is confusing to call it like that.

}
);
return result || [];
return isUndefined(html) ? [] : evaluateTemplate(vm, html);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could probably make this more clear, maybe adding a comment saying that html can only be undefined if there is an error on render.call() that is captured by a boundary.

Copy link
Contributor Author

@ravijayaramappa ravijayaramappa Sep 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This PR introduces a slight inconsistency in the way we handle invalid templates returned by invoking render().
if the render() returns an undefined

  1. In dev mode, we assert and throw, invoke errorCallback in boundary protection
  2. In prod mode, we do not throw. We return a blank template to gracefully degrade

if the render() returns a null/string/non-function

  1. In dev mode, we assert and throw, invoke errorCallback in boundary protection
  2. In prod mode, we do not assert, the framework will encounter a TypeError while trying to use the return value like a function. We invoke boundary protection.

I feel, throwing in dev mode and returning a blank template in prod mode is better user experience. Want to hear opinions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not the same we have in master today... in master today, if you return undefined, in dev or prod, it throws.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in fact it is not about undefined, it is about a registered template.

Copy link
Contributor

@caridy caridy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor things... but it is looking good.

@salesforce-best-lwc-internal
Copy link

Benchmark results

Base commit: 705b587 | Target commit: dc1cb3a

lwc-engine-benchmark

table-append-1k metric base(705b587) target(dc1cb3a) trend
table-clear-1k metric base(705b587) target(dc1cb3a) trend
table-create-10k metric base(705b587) target(dc1cb3a) trend
table-create-1k metric base(705b587) target(dc1cb3a) trend
table-update-10th-1k metric base(705b587) target(dc1cb3a) trend
tablecmp-append-1k metric base(705b587) target(dc1cb3a) trend
tablecmp-clear-1k metric base(705b587) target(dc1cb3a) trend
tablecmp-create-10k metric base(705b587) target(dc1cb3a) trend
tablecmp-create-1k metric base(705b587) target(dc1cb3a) trend
tablecmp-update-10th-1k metric base(705b587) target(dc1cb3a) trend
wc-append-1k metric base(705b587) target(dc1cb3a) trend
wc-clear-1k metric base(705b587) target(dc1cb3a) trend
wc-create-10k metric base(705b587) target(dc1cb3a) trend
wc-create-1k metric base(705b587) target(dc1cb3a) trend
wc-update-10th-1k metric base(705b587) target(dc1cb3a) trend

1 similar comment
@salesforce-best-lwc-internal
Copy link

Benchmark results

Base commit: 705b587 | Target commit: dc1cb3a

lwc-engine-benchmark

table-append-1k metric base(705b587) target(dc1cb3a) trend
table-clear-1k metric base(705b587) target(dc1cb3a) trend
table-create-10k metric base(705b587) target(dc1cb3a) trend
table-create-1k metric base(705b587) target(dc1cb3a) trend
table-update-10th-1k metric base(705b587) target(dc1cb3a) trend
tablecmp-append-1k metric base(705b587) target(dc1cb3a) trend
tablecmp-clear-1k metric base(705b587) target(dc1cb3a) trend
tablecmp-create-10k metric base(705b587) target(dc1cb3a) trend
tablecmp-create-1k metric base(705b587) target(dc1cb3a) trend
tablecmp-update-10th-1k metric base(705b587) target(dc1cb3a) trend
wc-append-1k metric base(705b587) target(dc1cb3a) trend
wc-clear-1k metric base(705b587) target(dc1cb3a) trend
wc-create-10k metric base(705b587) target(dc1cb3a) trend
wc-create-1k metric base(705b587) target(dc1cb3a) trend
wc-update-10th-1k metric base(705b587) target(dc1cb3a) trend

@salesforce-best-lwc-internal

This comment has been minimized.

@@ -609,10 +615,10 @@ export function ll(
id: string,
context?: (...args: any[]) => any
): EventListener {
if (isNull(vmBeingRendered)) {
const vm: VM | null = getVMBeingRendered();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessary type declaration for const.

Copy link
Contributor

@caridy caridy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, probably adding more tests to cover the cases of the original behavior, so we don't break it in the future.

@salesforce-best-lwc-internal
Copy link

Benchmark results

Base commit: c6cde9c | Target commit: ea776f2

lwc-engine-benchmark

table-append-1k metric base(c6cde9c) target(ea776f2) trend
table-clear-1k metric base(c6cde9c) target(ea776f2) trend
table-create-10k metric base(c6cde9c) target(ea776f2) trend
table-create-1k metric base(c6cde9c) target(ea776f2) trend
table-update-10th-1k metric base(c6cde9c) target(ea776f2) trend
tablecmp-append-1k metric base(c6cde9c) target(ea776f2) trend
tablecmp-clear-1k metric base(c6cde9c) target(ea776f2) trend
tablecmp-create-10k metric base(c6cde9c) target(ea776f2) trend
tablecmp-create-1k metric base(c6cde9c) target(ea776f2) trend
tablecmp-update-10th-1k metric base(c6cde9c) target(ea776f2) trend
wc-append-1k metric base(c6cde9c) target(ea776f2) trend
wc-clear-1k metric base(c6cde9c) target(ea776f2) trend
wc-create-10k metric base(c6cde9c) target(ea776f2) trend
wc-create-1k metric base(c6cde9c) target(ea776f2) trend
wc-update-10th-1k metric base(c6cde9c) target(ea776f2) trend

@ravijayaramappa ravijayaramappa merged commit 926f8f0 into winter20 Oct 3, 2019
@ravijayaramappa ravijayaramappa deleted the ravi/winter20/rendering-invariant-refactor/W-6572707 branch October 3, 2019 17:31
@ravijayaramappa
Copy link
Contributor Author

Will add tests for the original behavior when this change will be ported to master branch.

ravijayaramappa added a commit that referenced this pull request Oct 3, 2019
* test: automation for issue#1506
test case: A parent switches template and a child being disconnected sets a tracked property in  disconnectedCallback
* fix: mark template evaluation as a separate global flag
* fix: split boundary protection for render into two steps
ravijayaramappa added a commit that referenced this pull request Oct 14, 2019
* fix(engine): refactor boundary protection for render phase (#1507)

* test: automation for issue#1506
test case: A parent switches template and a child being disconnected sets a tracked property in  disconnectedCallback
* fix: mark template evaluation as a separate global flag
* fix: split boundary protection for render into two steps

* test: testcase for an existing behavior

test: run test only if global promise rejection handler is supported
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants