-
Notifications
You must be signed in to change notification settings - Fork 26.9k
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
Dispose Paragraph objects #110627
Conversation
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. |
Will also need flutter/engine#35835 |
}, skip: isBrowser && !isCanvasKit); // https://github.com/flutter/flutter/issues/56308 | ||
|
||
test('TextPainter - debugDisposed', () { | ||
final TextPainter painter = TextPainter(); |
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.
Check that at this point debugDispose
is false
?
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.
Done
|
||
/// Releases the resources associated with this painter. | ||
/// | ||
/// After disposal, this painter is unusable. |
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.
nit: I think this sentence does not need a comma.
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.
Done
/// | ||
/// Only for use when asserts are enabled. | ||
bool get debugDisposed { | ||
bool disposed = false; |
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 throw instead of returning false
in non-debug modes?
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.
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. |
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.
I think we typically refer to it as "debug mode", since on the command-line the option is --debug
.
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.
Some customers disable asserts in debug mode.
|
||
return painter.maxIntrinsicWidth; | ||
try { | ||
return painter.maxIntrinsicWidth; |
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.
Using try blocks for control flow is kind of an anti-pattern. Consider storing the return value in a variable, then returning the variable.
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.
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(); |
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.
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?
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.
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.
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 Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
Golden file changes are available for triage from new commit, Click here to view. For more guidance, visit Writing a golden file test for Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
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.
The text part lgtm (modulo comments from other reviewers). But for my own edification, are all NativeFieldWrapperClass
es 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 TextPainter
s?).
@@ -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); |
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.
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?
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.
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.
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.
It seems paragraphs can get different disposal signals:
- Leak: the developer doesn't call
dispose
, so a live paragraph is GC'd andFinalizationRegistry
is pinged about it. - Normal: developer calls
dispose
, then the GC collects the Dart wrapper and pings theFinalizationRegistry
about it.
Do our tests cover both situations?
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.
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.
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.
Anyone this breaks is already broken - they're relying on GC for memory clean up of possibly a single native object. |
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.
It's likely because of the fewer GC calls. We really need to add GC time totals to these too... |
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.