-
Notifications
You must be signed in to change notification settings - Fork 29.7k
_DefaultTabControllerState should dispose all created TabContoller instances. [prod-leak-fix] #136608
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
_DefaultTabControllerState should dispose all created TabContoller instances. [prod-leak-fix] #136608
Conversation
|
cc @polina-c |
| /// | ||
| /// This instance of [TabController] must not be used anymore and has to be | ||
| /// disposed since [AnimationController] was injected into the new | ||
| /// [TabController]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this true? May be it is up to invoker if the instance can be used or not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be honest, usage of the TabController after the removal of AnimationController from it (after the call of _copyWith) is impossible because we have several “bang operators” which will throw:
| _animationController!.value = index.toDouble(); |
| _animationController! |
| _animationController!.value = _index.toDouble(); |
| double get offset => _animationController!.value - _index.toDouble(); |
| _animationController!.value = value + _index.toDouble(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method does not do anything with the original instance, that makes it unusable, so it should not have opinion about what to do with it.
I suggest:
- Move
_animationController = null;out of this method to the invokers, because the methods name does not assume any updates like this. - Revert update do the comment.
How does it sound?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@polina-c, thanks for the suggestions. According to what you suggested, here is the new code:
@override
void didUpdateWidget(DefaultTabController oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.length != widget.length) {
...
final TabController newController = _controller._copyWith...
_controller._animationController = null;
_controller.dispose();
_controller = newController;
}
if (oldWidget.animationDuration != widget.animationDuration) {
final TabController newController = _controller._copyWith...
_controller._animationController = null;
_controller.dispose();
_controller = newController;
}
}What do you think?
Does explicit nulling of the private member look good to you?
| _controller.dispose(); | ||
| _controller = newController; | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about making it more readable:
TabController newController = _controller;
if (oldWidget.length != widget.length) {
newController = newController._copyWith...
}
if (oldWidget.animationDuration != widget.animationDuration) {
newController = newController._copyWith...
}
if (newController != _controller) {
_controller.dispose();
_controller = newController;
_animationController = null;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@polina-c, thanks a lot for your suggestion, it looks much cleaner. But if we do it in such a way, then we will lose the reference to the first new TabController if the length and animationDuration are changed at the same time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really? Both _copyWith are invoked for newController, not for _controller. so, nothing is lost.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I am missing something, but here is how I see the flow in case both length and animationDuration was updated:
// newController = TabController0
TabController newController = _controller;
if (oldWidget.length != widget.length) {
// newController = TabController1
newController = newController._copyWith...
}
if (oldWidget.animationDuration != widget.animationDuration) {
// newController = TabController2
newController = newController._copyWith...
}
if (newController != _controller) {
_controller._animationController = null;
// TabController0.dispose()
_controller.dispose();
// _controller = TabController2
_controller = newController;
// TabController1 is lost
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see now.
You are right.
Thanks for explanation.
Then the only question is about _animationController = null;:
- Why we need it? Old controller is not referenced and is disposed, so what's the point to null _animationController?
- If it is needed, (1) it does not seem
_copyWithis right method to do it and (2) there should be comment that explains why it is needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the review.
Under the hood, _copyWith transfers AnimationController from one TabController to another.
/// Creates a new [TabController] with `index`, `previousIndex`, `length`, and
/// `animationDuration` if they are non-null.
///
/// This method is used by [DefaultTabController].
///
/// When [DefaultTabController.length] is updated, this method is called to
/// create a new [TabController] without creating a new [AnimationController].
TabController _copyWith({
...
}) {
...
return TabController._(
...
animationController: _animationController,
....
);
}In dispose method of the TabController we have disposal of the AnimationController.
@override
void dispose() {
_animationController?.dispose();
_animationController = null;
super.dispose();
}We can't hold the same AnimationController in both TabControllers because it will cause double disposal in the future. So, to not dispose recently injected AnimationController in the old TabController, we need to null it. It will be disposed in the new TabController in the future.
This explains why I added nulling of the _animationController in the _copyWith method and added this comment:
/// This instance of [TabController] must not be used anymore and has to be
/// disposed since [AnimationController] was injected into the new
/// [TabController].
What can you suggest to improve or change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. Thanks.
Then _copyWith is now doing way more than just copying.
How about renaming _copyWith to _copyWithAndDispose, and making it fully disposing the original instance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great suggestion, thanks!
Pushed the new version.
|
@polina-c, thanks for help and review! |
flutter/flutter@c2bd2c1...0883cb2 2023-10-20 [email protected] Roll Packages from 09c6b11 to be915be (6 revisions) (flutter/flutter#136964) 2023-10-20 [email protected] Roll Flutter Engine from de780872533c to 9b88ff83fd82 (4 revisions) (flutter/flutter#136959) 2023-10-20 [email protected] Roll Flutter Engine from 1e107c21328a to de780872533c (1 revision) (flutter/flutter#136952) 2023-10-20 [email protected] Roll Flutter Engine from fd33e7e75eac to 1e107c21328a (1 revision) (flutter/flutter#136949) 2023-10-20 [email protected] Roll Flutter Engine from 9dd59f7fcff9 to fd33e7e75eac (1 revision) (flutter/flutter#136942) 2023-10-20 [email protected] Roll Flutter Engine from cc3356c0e68f to 9dd59f7fcff9 (1 revision) (flutter/flutter#136935) 2023-10-20 [email protected] Roll Flutter Engine from 504a99d93f42 to cc3356c0e68f (2 revisions) (flutter/flutter#136934) 2023-10-19 [email protected] Roll Flutter Engine from 503b84295462 to 504a99d93f42 (3 revisions) (flutter/flutter#136930) 2023-10-19 [email protected] Roll Flutter Engine from 3b0469b0c718 to 503b84295462 (1 revision) (flutter/flutter#136928) 2023-10-19 [email protected] Revert "[Velocity Tracker] Fix: Issue 97761: Flutter Scrolling does not match iOS; inadvertent scrolling when user lifts up finger" (flutter/flutter#136905) 2023-10-19 [email protected] [flutter_tools] move build_preview_test from commands/permeable to integration shard (flutter/flutter#136912) 2023-10-19 [email protected] Roll Flutter Engine from b40042ebb95b to 3b0469b0c718 (2 revisions) (flutter/flutter#136922) 2023-10-19 [email protected] do not include entries from `--dart-define-from-file` files in the gradle config or environment during build (flutter/flutter#136865) 2023-10-19 [email protected] Roll Flutter Engine from bfd2ffb9a8bc to b40042ebb95b (2 revisions) (flutter/flutter#136915) 2023-10-19 [email protected] Roll Flutter Engine from 9d49175618f5 to bfd2ffb9a8bc (2 revisions) (flutter/flutter#136910) 2023-10-19 [email protected] Allow users to customize search algorithm in `DropdownMenu` (flutter/flutter#136848) 2023-10-19 [email protected] Upgrade Flutter deps to pull in latest vm_service and dwds (flutter/flutter#136734) 2023-10-19 [email protected] [Reland] Skip injecting Bonjour settings when port publication is disabled (flutter/flutter#136842) 2023-10-19 [email protected] _DefaultTabControllerState should dispose all created TabContoller instances. (flutter/flutter#136608) 2023-10-19 [email protected] Reland: "Add code for updating `focusedChild` when removing grandchildren from scope" (flutter/flutter#136899) 2023-10-19 [email protected] Roll Flutter Engine from 418dce4feaf7 to 9d49175618f5 (1 revision) (flutter/flutter#136901) 2023-10-19 [email protected] Unmark linux_android platform_channels_benchmarks as flaky (flutter/flutter#136838) 2023-10-19 [email protected] Roll Packages from 14aa69e to 09c6b11 (4 revisions) (flutter/flutter#136896) 2023-10-19 [email protected] Roll Flutter Engine from 28cb2508b8e0 to 418dce4feaf7 (2 revisions) (flutter/flutter#136895) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC [email protected],[email protected],[email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
…oller instances. (flutter#136608)" This reverts commit 9fa9fd3.
…oller instances. (#136608)" (#144579) This reverts commit 9fa9fd3. Fixes #144087. Fixes #138588. This crash has been reported previously from a customer in google3 in #138588, but we weren't able to track it down. Thankfully, a repro was now provided in #144087 (comment) which traced the crash down to a change made in #136608. This PR reverts that change to fix that crash for now. I will post a new PR shortly that will add a test to cover the use case that caused the crash with #136608 to make sure we don't re-introduce the crash in the future.
Description
Tests
material/tabs_test.dartto usetestWidgetsWithLeakTracking.Pre-launch Checklist
///).