Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Clarify the relationship between PictureRecorder and Canvas #19393

Merged
merged 1 commit into from
Jul 9, 2020

Conversation

jason-simmons
Copy link
Member

A Canvas is only valid as long as the PictureRecorder used to construct
the Canvas is alive. Previously Dart was unaware of this relationship,
and it was possible for a PictureRecorder to be GCed before the Canvas.

  • Canvas will hold a reference to its PictureRecorder in order to
    prevent the PictureRecorder from being GCed until endRecording
    is called or the Canvas itself is GCed.
  • PictureRecorder.endRecording will disconnect the Canvas native object
    from its Dart peer. Attempts to use the Canvas after endRecoding
    will result in an "Object has been disposed" exception thrown by
    Tonic.

See flutter/flutter#60319
See flutter/flutter#51066

@fluttergithubbot
Copy link
Contributor

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat.

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

@auto-assign auto-assign bot requested a review from gw280 June 29, 2020 22:27
Copy link
Member

@chinmaygarde chinmaygarde left a comment

Choose a reason for hiding this comment

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

I may be mistaken but the way this reads to me is "the canvas references a recorder and a recorder references a canvas with the association broken when the caller ends recording". So, if a canvas and recorder are created by the caller but end recording is not called before both are unreachable, won't they both never be collected because their native peers still hold references to them?

@@ -41,7 +39,7 @@ PictureRecorder::PictureRecorder() {}
PictureRecorder::~PictureRecorder() {}

bool PictureRecorder::isRecording() {
return canvas_ && canvas_->IsRecording();
return bool(canvas_);
Copy link
Member

Choose a reason for hiding this comment

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

static_cast instead of C style casts please.

Copy link
Member Author

Choose a reason for hiding this comment

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

FWIW, this is a C++ bool constructor invocation. Removed this.

@@ -41,7 +39,7 @@ PictureRecorder::PictureRecorder() {}
PictureRecorder::~PictureRecorder() {}

bool PictureRecorder::isRecording() {
Copy link
Member

Choose a reason for hiding this comment

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

This is now unused right (Dart won't call this anymore)? Can we just remove this method?

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 was being used by an assert in the Canvas constructor, but that check is redundant with checks done in Dart. Removed this.

@@ -432,12 +432,11 @@ void Canvas::drawShadow(const CanvasPath* path,
elevation, transparentOccluder, dpr);
}

void Canvas::Clear() {
void Canvas::Invalidate() {
if (dart_wrapper()) {
Copy link
Member

Choose a reason for hiding this comment

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

Who else invalidates the canvas? The only owner would be a picture recorder which gets its own bespoke canvas. In case the caller tries to end recording multiple time, the isRecording check on the wrapper (PictureRecorder) would trip. Not sure when the Dart wrapper would ever be unset here. Seems like a redundant check.

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 is defensive coding intended to avoid the scenario described in flutter/flutter#60319 (comment) where a Dart API invocation causes the Canvas to be GCed.

If the PictureRecorder Dart object is still holding its reference to the Canvas when the endRecording native method is called, then it would not be possible for the Canvas to be GCed inside the native method. But I wanted to add the check so that Invalidate will still be safe if the order of operations in the Dart code changes.

@jason-simmons
Copy link
Member Author

The native peers hold weak references to the Dart objects, which will not prevent the PictureRecorder and Canvas from being GCed.

Even if endRecording is never called Dart can still GC the PictureRecorder and Canvas while they still hold references to each other.

A Canvas is only valid as long as the PictureRecorder used to construct
the Canvas is alive.  Previously Dart was unaware of this relationship,
and it was possible for a PictureRecorder to be GCed before the Canvas.

* Canvas will hold a reference to its PictureRecorder in order to
  prevent the PictureRecorder from being GCed until endRecording
  is called or the Canvas itself is GCed.
* PictureRecorder.endRecording will disconnect the Canvas native object
  from its Dart peer.  Attempts to use the Canvas after endRecoding
  will result in an "Object has been disposed" exception thrown by
  Tonic.

See flutter/flutter#60319
See flutter/flutter#51066
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
cla: yes waiting for tree to go green This PR is approved and tested, but waiting for the tree to be green to land.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants