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

Dispose Paragraph objects #110627

Merged
merged 4 commits into from
Sep 6, 2022
Merged

Dispose Paragraph objects #110627

merged 4 commits into from
Sep 6, 2022

Conversation

dnfield
Copy link
Contributor

@dnfield dnfield commented Aug 30, 2022

Addresses most of #110601, except one case that's a bit difficult right now - the FlutterLogo decoration does not have a lifecycle and creates a text painter.

Once this is done, we can remove the bogus accounting we do for paragraph object size in dart:ui.

This patch also includes one stray vertices.dispose, since I added that API in the same engine patch as the one for Paragraph and they're both now available.

@flutter-dashboard flutter-dashboard bot added a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) a: text input Entering text in a text field or keyboard related problems f: cupertino flutter/packages/flutter/cupertino repository f: material design flutter/packages/flutter/material repository. f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels. team Infra upgrades, team productivity, code health, technical debt. See also team: labels. labels Aug 30, 2022
@dnfield
Copy link
Contributor Author

dnfield commented Aug 30, 2022

Will need flutter/engine#35815

Also needs something else for HTML - I'll need to take more time to figure out if we're using a disposed object becuase we're disposing it too early or if it's because we should stop trying to use it.

@dnfield
Copy link
Contributor Author

dnfield commented Aug 31, 2022

Will also need flutter/engine#35835

@dnfield dnfield requested a review from yjbanov September 1, 2022 04:14
}, skip: isBrowser && !isCanvasKit); // https://github.com/flutter/flutter/issues/56308

test('TextPainter - debugDisposed', () {
final TextPainter painter = TextPainter();
Copy link
Contributor

Choose a reason for hiding this comment

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

Check that at this point debugDispose is 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.

Done


/// Releases the resources associated with this painter.
///
/// After disposal, this painter is unusable.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I think this sentence does not need a comma.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

///
/// Only for use when asserts are enabled.
bool get debugDisposed {
bool disposed = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe throw instead of returning false in non-debug modes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. I think we should come up with a unified strategy for this and put it in the style guide. I'm somewhat inclined to think throwing is too aggressive, but we can hash that out separately from this PR.


/// Whether this object has been disposed or not.
///
/// Only for use when asserts are enabled.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we typically refer to it as "debug mode", since on the command-line the option is --debug.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Some customers disable asserts in debug mode.


return painter.maxIntrinsicWidth;
try {
return painter.maxIntrinsicWidth;
Copy link
Contributor

Choose a reason for hiding this comment

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

Using try blocks for control flow is kind of an anti-pattern. Consider storing the return value in a variable, then returning the variable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We want to make sure to dispose in case an exception gets thrown by the getter.

@@ -1070,6 +1070,7 @@ void main() {
final ui.Paragraph paragraph = builder.build();
paragraph.layout(const ui.ParagraphConstraints(width: 1000));
expect(paragraph.getBoxesForRange(2, 2), isEmpty);
paragraph.dispose();
Copy link
Contributor

Choose a reason for hiding this comment

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

WidgetTester performs checks for dangling app state, such as mouse pointers. Should we also have it proactively look for leaked paragraph?

Or perhaps the leak detector that @polina-c is working on can help here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We'd need changes in dart:ui to support this.

Polina's work should help here. There's another option where we make the engine itself aware of this kind of thing and throw an exception when it happens, but that's possibly a heavy hammer and prone to flakes.

@flutter-dashboard
Copy link

Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change).

If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Changes reported for pull request #110627 at sha f0d4cfe

@flutter-dashboard flutter-dashboard bot added the will affect goldens Changes to golden files label Sep 1, 2022
@flutter-dashboard
Copy link

Golden file changes are available for triage from new commit, Click here to view.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Changes reported for pull request #110627 at sha 7e2fe2a

Copy link
Contributor

@LongCatIsLooong LongCatIsLooong left a comment

Choose a reason for hiding this comment

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

The text part lgtm (modulo comments from other reviewers). But for my own edification, are all NativeFieldWrapperClasses need a dispose method now or it's case-by-case?

Also there are 153 references to the TextPainter constructor in g3. By definition this is not a breaking change but to me this feels quite breaking (if developers don't update they're going to start leaking TextPainters?).

@@ -144,8 +153,8 @@ class BannerPainter extends CustomPainter {
..drawRect(_kRect, _paintShadow)
..drawRect(_kRect, _paintBanner);
const double width = _kOffset * 2.0;
_textPainter.layout(minWidth: width, maxWidth: width);
_textPainter.paint(canvas, _kRect.topLeft + Offset(0.0, (_kRect.height - _textPainter.height) / 2.0));
_textPainter!.layout(minWidth: width, maxWidth: width);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: nothing resets _parepared to false so _textPainter is a lazily initialized final var. Maybe change _textPainter back to final late so we can remove the exclamation marks here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Developers are already leaking text painters today, they just have no way of disposing them. We should send out an announcement though and I can look into doing a g3 CL.

After dispose, the object should not be used - I don't think resetting prepared makes sense there, but we could add asserts about making sure methods aren't called after disposal.

And having a non-nullable but disposable object is often a little tricky, it's better to be able to null it out to prevent usage after disposal.

Copy link
Contributor

Choose a reason for hiding this comment

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

It seems paragraphs can get different disposal signals:

  • Leak: the developer doesn't call dispose, so a live paragraph is GC'd and FinalizationRegistry is pinged about it.
  • Normal: developer calls dispose, then the GC collects the Dart wrapper and pings the FinalizationRegistry about it.

Do our tests cover both situations?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't yet have a good way to cover an object that gets finalized without getting disposed, but @polina-c is working on that.

@dnfield
Copy link
Contributor Author

dnfield commented Sep 6, 2022

The text part lgtm (modulo comments from other reviewers). But for my own edification, are all NativeFieldWrapperClasses need a dispose method now or it's case-by-case?

In general we should be disposing NativeFieldWrapperClasses, but it's not really important for ones that are very simple data classes. However, if they are just a simple data class, they probably should just be plain Dart objects to begin with.

The idea is: if it holds a large chunk of native/graphics memory, we should not be relying on GC to get that cleaned up. If it is only holding primitives that aren't that big, it's less critical to clean that up quickly but also probably never needed a C++ peer anyway.

Also there are 153 references to the TextPainter constructor in g3. By definition this is not a breaking change but to me this feels quite breaking (if developers don't update they're going to start leaking TextPainters?).

Anyone this breaks is already broken - they're relying on GC for memory clean up of possibly a single native object.

@dnfield dnfield requested a review from yjbanov September 6, 2022 19:36
Copy link
Contributor

@yjbanov yjbanov left a comment

Choose a reason for hiding this comment

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

lgtm

@dnfield dnfield added the autosubmit Merge PR when tree becomes green via auto submit App label Sep 6, 2022
@auto-submit auto-submit bot merged commit eaff156 into flutter:master Sep 6, 2022
@dnfield dnfield deleted the text_disp branch September 6, 2022 20:49
@dnfield
Copy link
Contributor Author

dnfield commented Sep 12, 2022

It's likely because of the fewer GC calls. We really need to add GC time totals to these too...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) a: text input Entering text in a text field or keyboard related problems autosubmit Merge PR when tree becomes green via auto submit App f: cupertino flutter/packages/flutter/cupertino repository f: material design flutter/packages/flutter/material repository. f: scrolling Viewports, list views, slivers, etc. framework flutter/packages/flutter repository. See also f: labels. team Infra upgrades, team productivity, code health, technical debt. See also team: labels. will affect goldens Changes to golden files
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants