-
Notifications
You must be signed in to change notification settings - Fork 6k
Do not capture a reference to the ResourceContext in the lambda created in the UnrefQueue dtor #33766
Conversation
| 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); |
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 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();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 also need to add a comment since this construct is odd and any usage of verboten raw pointers should come with an explanation)
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.
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.
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.
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.
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 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();
});
}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.
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?
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.
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.
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 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.
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.
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.
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.
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
gaaclarke
left a comment
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.
LGTM with a suggestion to the comment. Thanks for talking this through, it was pretty gnarly. Awesome catch.
| // 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. |
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 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_`. |
…da created in the UnrefQueue dtor (flutter/engine#33766)
…ed in the UnrefQueue dtor (flutter#33766)
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