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

Restrict DeepMerger mutable object reuse to fix subtle production-only bug #9742

Merged
merged 3 commits into from
May 23, 2022

Conversation

benjamn
Copy link
Member

@benjamn benjamn commented May 19, 2022

Issue #9735 appears to be due to a bug I introduced in my commit 756ab87 in PR #8734. This PR takes us (partially) back to the way things worked before that commit.

I believe I've found two complementary ways to fix this problem:

Stop using shared DeepMerger in ReadContext for executeSelectionSet

Premature optimization has a bad reputation already, but you can add this PR to the mountain of evidence in its disfavor.

Avoid using Object.isFrozen to prevent dev/prod differences

If you revert the previous commit and run npm test, you'll see all the tests this dynamic Object.isFrozen check has been silently protecting from failing, but only (and this is the important part) in development.

Since we only actually freeze objects with Object.freeze in development, this Object.isFrozen check does not help in production, so an object that would have been frozen in development could be reused as a mutable copy in production, potentially acquiring properties it should not acquire (a bug fixed by the previous commit, first reported in issue #9735).

Thanks very much to @akallem for some deep debugging and an excellent reproduction in #9735.

@benjamn benjamn added this to the v3.6.x patch releases milestone May 19, 2022
@benjamn benjamn self-assigned this May 19, 2022
@benjamn benjamn requested review from brainkim and hwillson May 19, 2022 22:29
Comment on lines -249 to -257
merge(a, b) {
// We use the same DeepMerger instance throughout the read, so any
// merged objects created during this read can be updated later in the
// read using in-place/destructive property assignments. Once the read
// is finished, these objects will be frozen, but in the meantime it's
// good for performance and memory usage if we avoid allocating a new
// object for every merged property.
return merger.merge(a, b);
},
Copy link
Member Author

Choose a reason for hiding this comment

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

This comment might have been right about it being "good for performance and memory usage if we avoid allocating a new object for every merged property," in theory, but you have to be prepared to abandon any optimization that causes bugs, as this one did.

@benjamn
Copy link
Member Author

benjamn commented May 19, 2022

@akallem @brainkim These changes can be tested now by running npm i @apollo/client@beta to get version 3.7.0-alpha.6. Though that version says 3.7, this PR is targeting the main branch, implying a 3.6.x patch release (not 3.7).

@akallem
Copy link

akallem commented May 20, 2022

LGTM. I've checked and confirmed that this does resolve the bug in the sample reproduction in #9735.

Copy link
Contributor

@brainkim brainkim left a comment

Choose a reason for hiding this comment

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

Good catch! Thanks Ben.

Issue #9735 appears to be due to a bug I introduced in my commit
756ab87 in PR #8734.

Premature optimization has a bad reputation already, but you can add
this PR to the mountain of evidence in its disfavor.
If you revert the previous commit and run `npm test`, you'll see all the
tests this dynamic Object.isFrozen check has been silently protecting
from failing, but only (and this is the important part) in development.

Since we only actually freeze objects with Object.freeze in development,
this Object.isFrozen check does not help in production, so an object
that would have been frozen in development gets reused as a mutable
copy, potentially acquiring properties it should not acquire (a bug
fixed by the previous commit, first reported in issue #9735).
@benjamn benjamn force-pushed the issue-9735-DeepMerger-prod-only-mismerge branch from 916f974 to 570b7f3 Compare May 23, 2022 21:18
@benjamn benjamn merged commit 5fc6a39 into main May 23, 2022
@benjamn benjamn deleted the issue-9735-DeepMerger-prod-only-mismerge branch May 23, 2022 22:21
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 15, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
3 participants