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

Conversation

@jason-simmons
Copy link
Member

The lambda is destructed on the thread that runs the UnrefQueue dtor.
If the lambda itself owns a reference to the ResourceContext, then the
last reference to the ResourceContext could be deleted on the wrong thread.

See flutter/flutter#105066

@jason-simmons jason-simmons requested a review from gaaclarke June 1, 2022 23:17
task_runner_, [objects = std::move(objects_),
context = std::move(context_)]() mutable {
task_runner_, [objects = std::move(objects_), raw_context]() mutable {
sk_sp<ResourceContext> context(raw_context);
Copy link
Member

Choose a reason for hiding this comment

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

The problem here is that we don't know if there still exists an sk_sp that points to this context still, no? The release isn't guaranteeing that we are the only owner. I'm trying to think if there is an alternative. What if we instead created a sk_sp and the closure captured a pointer to that sk_sp which it then reset. That seems safer to me.

sk_sp<ResourceContext>* foo = new sk_sp<ResourceContext>(context_);
context_.reset();
fml::TaskRunner::RunNowOrPostTask(
        task_runner_, [objects = std::move(objects_), foo]() mutable {
          auto foo_ptr = std::make_unique<sk_sp<ResourceContext>>(foo);
          DoDrain(objects, *foo_ptr);
          *foo_ptr->reset();

Copy link
Member

Choose a reason for hiding this comment

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

(we also need to add a comment since this construct is odd and any usage of verboten raw pointers should come with an explanation)

Copy link
Member

Choose a reason for hiding this comment

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

After further thought, this is much safer. If we can guarantee that release is safe, we should shift the code to unique_ptr.

Also, in the example above you could use std::move between foo and context_ to make it a smidge faster. I'm having a hard time thinking up a cleaner way than what I suggested though.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added a comment.

I think that releasing the ResourceContext pointer on the UnrefQueue dtor thread and adopting it on the task thread is a clear way to express the intent.

Allocating and deleting another sk_sp on the heap is additional work that adds no value. You still need to properly handle the raw pointer to the sk_sp and transfer ownership of the raw pointer to a unique_ptr within the task.

There is no way to know if something else in the engine also owns a reference to the resource context. The only goal here is ensuring that the reference owned by the UnrefQueue is deleted on the proper thread.

Copy link
Member

Choose a reason for hiding this comment

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

I tried to grab the shared pointer from the closure with a move and it didn't fix the issue. I'm kind of surprised that didn't fix it, that would have been better. Maybe you know how to make it work:

  ~UnrefQueue() {
    fml::TaskRunner::RunNowOrPostTask(
        task_runner_, [objects = std::move(objects_),
                       context = std::move(context_)]() mutable {
          auto my_context = std::move(context);
          DoDrain(objects, my_context);
          my_context.reset();
        });
  }

Copy link
Member

Choose a reason for hiding this comment

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

Yea, i tried the following but it didn't work either:

  ~UnrefQueue() {
    fml::TaskRunner::RunNowOrPostTask(
        task_runner_, std::move([objects = std::move(objects_),
                                 context = std::move(context_)]() mutable {
          auto my_context = std::move(context);
          DoDrain(objects, my_context);
          my_context.reset();
        }));
  }

I guess there must be a copy being made somewhere on the dtor thread. It seems like we could probably chase that down, no?

Copy link
Member

Choose a reason for hiding this comment

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

No other references are added or removed here. The release/adopt just transfers that existing reference from the UnrefQueue to the deferred task.

I don't think I'm communicating this effectively. This is what I'm thinking:

sk_sp<Foo> foo = sk_sp<Foo>(new Foo);
sk_sp<UnrefQueue<Foo>> queue(task_runner, zero, foo);
queue.reset();
sleep(10);
foo.reset();  // This will be a double delete since we've released our ownership of foo, then recreated a smart pointer around it and deleted it.

If there is something blocking this from happening, I'm not seeing it.

Copy link
Member Author

Choose a reason for hiding this comment

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

The original lambda object is instantiated in the UnrefQueue dtor and then copied into a task queue by RunNowOrPostTask.

The original instance will be deleted on the thread that runs the UnrefQueue dtor. The instance in the task queue will be deleted on the thread associated with the task_runner_.

I don't think TaskRunner offers any way to avoid having two instances of the lambda.

Copy link
Member Author

Choose a reason for hiding this comment

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

sk_sp<Foo> foo = sk_sp<Foo>(new Foo);
sk_sp<UnrefQueue<Foo>> queue(task_runner, zero, foo);
queue.reset();
sleep(10);
foo.reset();  // This will be a double delete since we've released our ownership of foo, then recreated a smart pointer around it and deleted it.

This is not a double delete. The Foo object will be instantiated with an initial reference count of 1, owned by the foo smart pointer. The UnrefQueue will add another reference count owned by its internal smart pointer.

The reference owned by the UnrefQueue will be dropped by the UnrefQueue's dtor. The transfer of the reference from the dtor thread to the deferred task thread does not affect the reference count. The only change to the reference count happens when the task decrements the count.

The reference owned by foo will be dropped when foo.reset() is called.

The Foo object will be deleted when the last of these references is dropped and the reference count reaches zero.

Copy link
Member

Choose a reason for hiding this comment

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

Ahh, sk_sp stores the reference count on the object, not on the smart pointer. I forgot that. Let me look this again tomorrow with that in mind. Thanks.

…ed in the UnrefQueue dtor

The lambda is destructed on the thread that runs the UnrefQueue dtor.
If the lambda itself owns a reference to the ResourceContext, then the
last reference to the ResourceContext could be deleted on the wrong thread.

See flutter/flutter#105066
Copy link
Member

@gaaclarke gaaclarke left a comment

Choose a reason for hiding this comment

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

LGTM with a suggestion to the comment. Thanks for talking this through, it was pretty gnarly. Awesome catch.

Comment on lines +77 to +79
// The ResourceContext must be deleted on the task runner thread.
// Transfer ownership of the UnrefQueue's ResourceContext reference
// into a task queued to that thread.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// The ResourceContext must be deleted on the task runner thread.
// Transfer ownership of the UnrefQueue's ResourceContext reference
// into a task queued to that thread.
// The ResourceContext must be deleted on the task runner thread.
// This captures a raw pointer in the lambda to make sure the object isn't
// deleted when the lambda is deleted, but instead ensures it is deleted
// on `task_runner_`.

@zanderso zanderso added the waiting for tree to go green This PR is approved and tested, but waiting for the tree to be green to land. label Jun 2, 2022
@fluttergithubbot fluttergithubbot merged commit 1781823 into flutter:main Jun 2, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 2, 2022
houhuayong pushed a commit to houhuayong/engine that referenced this pull request Jun 21, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

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