Fix DelegatedFrameResourceCollection thread safety
Don't keep a ref to DelegatedFrameResourceCollection inside the return resources callback, because it is called from a thread. BUG=None R=danakj@chromium.org Review URL: https://codereview.chromium.org/47703005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@231915 0039d316-1c4b-4281-b951-d872f2087c98
This commit is contained in:
@ -24,6 +24,7 @@
|
||||
'layers/content_layer_unittest.cc',
|
||||
'layers/contents_scaling_layer_unittest.cc',
|
||||
'layers/delegated_frame_provider_unittest.cc',
|
||||
'layers/delegated_frame_resource_collection_unittest.cc',
|
||||
'layers/delegated_renderer_layer_impl_unittest.cc',
|
||||
'layers/heads_up_display_unittest.cc',
|
||||
'layers/heads_up_display_layer_impl_unittest.cc',
|
||||
|
@ -12,7 +12,8 @@ namespace cc {
|
||||
DelegatedFrameResourceCollection::DelegatedFrameResourceCollection()
|
||||
: client_(NULL),
|
||||
main_thread_runner_(BlockingTaskRunner::current()),
|
||||
lost_all_resources_(false) {
|
||||
lost_all_resources_(false),
|
||||
weak_ptr_factory_(this) {
|
||||
DCHECK(main_thread_checker_.CalledOnValidThread());
|
||||
}
|
||||
|
||||
@ -112,21 +113,21 @@ void DelegatedFrameResourceCollection::RefResources(
|
||||
resource_id_ref_count_map_[resources[i].id].refs_to_wait_for++;
|
||||
}
|
||||
|
||||
ReturnCallback
|
||||
DelegatedFrameResourceCollection::GetReturnResourcesCallbackForImplThread() {
|
||||
return base::Bind(
|
||||
&DelegatedFrameResourceCollection::UnrefResourcesOnImplThread,
|
||||
this,
|
||||
main_thread_runner_);
|
||||
}
|
||||
|
||||
void DelegatedFrameResourceCollection::UnrefResourcesOnImplThread(
|
||||
static void UnrefResourcesOnImplThread(
|
||||
base::WeakPtr<DelegatedFrameResourceCollection> self,
|
||||
scoped_refptr<BlockingTaskRunner> main_thread_runner,
|
||||
const ReturnedResourceArray& returned) {
|
||||
main_thread_runner->PostTask(
|
||||
FROM_HERE,
|
||||
base::Bind(
|
||||
&DelegatedFrameResourceCollection::UnrefResources, this, returned));
|
||||
&DelegatedFrameResourceCollection::UnrefResources, self, returned));
|
||||
}
|
||||
|
||||
ReturnCallback
|
||||
DelegatedFrameResourceCollection::GetReturnResourcesCallbackForImplThread() {
|
||||
return base::Bind(&UnrefResourcesOnImplThread,
|
||||
weak_ptr_factory_.GetWeakPtr(),
|
||||
main_thread_runner_);
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "base/containers/hash_tables.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/threading/thread_checker.h"
|
||||
#include "cc/base/cc_export.h"
|
||||
#include "cc/resources/return_callback.h"
|
||||
@ -46,10 +47,6 @@ class CC_EXPORT DelegatedFrameResourceCollection
|
||||
friend class base::RefCounted<DelegatedFrameResourceCollection>;
|
||||
~DelegatedFrameResourceCollection();
|
||||
|
||||
void UnrefResourcesOnImplThread(
|
||||
scoped_refptr<BlockingTaskRunner> main_thread_runner,
|
||||
const ReturnedResourceArray& returned);
|
||||
|
||||
DelegatedFrameResourceCollectionClient* client_;
|
||||
scoped_refptr<BlockingTaskRunner> main_thread_runner_;
|
||||
|
||||
@ -64,6 +61,7 @@ class CC_EXPORT DelegatedFrameResourceCollection
|
||||
ResourceIdRefCountMap resource_id_ref_count_map_;
|
||||
|
||||
base::ThreadChecker main_thread_checker_;
|
||||
base::WeakPtrFactory<DelegatedFrameResourceCollection> weak_ptr_factory_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(DelegatedFrameResourceCollection);
|
||||
};
|
||||
|
158
cc/layers/delegated_frame_resource_collection_unittest.cc
Normal file
158
cc/layers/delegated_frame_resource_collection_unittest.cc
Normal file
@ -0,0 +1,158 @@
|
||||
// Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/run_loop.h"
|
||||
#include "base/synchronization/waitable_event.h"
|
||||
#include "base/threading/thread.h"
|
||||
#include "cc/layers/delegated_frame_resource_collection.h"
|
||||
#include "cc/resources/returned_resource.h"
|
||||
#include "cc/resources/transferable_resource.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace cc {
|
||||
namespace {
|
||||
|
||||
class DelegatedFrameResourceCollectionTest
|
||||
: public testing::Test,
|
||||
public DelegatedFrameResourceCollectionClient {
|
||||
protected:
|
||||
DelegatedFrameResourceCollectionTest() : resources_available_(false) {}
|
||||
|
||||
virtual void SetUp() OVERRIDE { CreateResourceCollection(); }
|
||||
|
||||
virtual void TearDown() OVERRIDE { DestroyResourceCollection(); }
|
||||
|
||||
void CreateResourceCollection() {
|
||||
DCHECK(!resource_collection_);
|
||||
resource_collection_ = new DelegatedFrameResourceCollection;
|
||||
resource_collection_->SetClient(this);
|
||||
}
|
||||
|
||||
void DestroyResourceCollection() {
|
||||
if (resource_collection_) {
|
||||
resource_collection_->SetClient(NULL);
|
||||
resource_collection_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
TransferableResourceArray CreateResourceArray() {
|
||||
TransferableResourceArray resources;
|
||||
TransferableResource resource;
|
||||
resource.id = 444;
|
||||
resources.push_back(resource);
|
||||
return resources;
|
||||
}
|
||||
|
||||
virtual void UnusedResourcesAreAvailable() OVERRIDE {
|
||||
resources_available_ = true;
|
||||
resource_collection_->TakeUnusedResourcesForChildCompositor(
|
||||
&returned_resources_);
|
||||
if (!resources_available_closure_.is_null())
|
||||
resources_available_closure_.Run();
|
||||
}
|
||||
|
||||
bool ReturnAndResetResourcesAvailable() {
|
||||
bool r = resources_available_;
|
||||
resources_available_ = false;
|
||||
return r;
|
||||
}
|
||||
|
||||
scoped_refptr<DelegatedFrameResourceCollection> resource_collection_;
|
||||
bool resources_available_;
|
||||
ReturnedResourceArray returned_resources_;
|
||||
base::Closure resources_available_closure_;
|
||||
};
|
||||
|
||||
// This checks that taking the return callback doesn't take extra refcounts,
|
||||
// since it's sent to other threads.
|
||||
TEST_F(DelegatedFrameResourceCollectionTest, NoRef) {
|
||||
// Start with one ref.
|
||||
EXPECT_TRUE(resource_collection_->HasOneRef());
|
||||
|
||||
ReturnCallback return_callback =
|
||||
resource_collection_->GetReturnResourcesCallbackForImplThread();
|
||||
|
||||
// Callback shouldn't take a ref since it's sent to other threads.
|
||||
EXPECT_TRUE(resource_collection_->HasOneRef());
|
||||
}
|
||||
|
||||
void ReturnResourcesOnThread(ReturnCallback callback,
|
||||
const ReturnedResourceArray& resources,
|
||||
base::WaitableEvent* event) {
|
||||
callback.Run(resources);
|
||||
event->Wait();
|
||||
}
|
||||
|
||||
// Tests that the ReturnCallback can run safely on threads even after the
|
||||
// last references to the collection were dropped.
|
||||
TEST_F(DelegatedFrameResourceCollectionTest, Thread) {
|
||||
base::Thread thread("test thread");
|
||||
thread.Start();
|
||||
|
||||
TransferableResourceArray resources = CreateResourceArray();
|
||||
resource_collection_->ReceivedResources(resources);
|
||||
resource_collection_->RefResources(resources);
|
||||
|
||||
ReturnedResourceArray returned_resources;
|
||||
TransferableResource::ReturnResources(resources, &returned_resources);
|
||||
|
||||
base::WaitableEvent event(false, false);
|
||||
|
||||
{
|
||||
base::RunLoop run_loop;
|
||||
resources_available_closure_ = run_loop.QuitClosure();
|
||||
|
||||
thread.message_loop()->PostTask(
|
||||
FROM_HERE,
|
||||
base::Bind(
|
||||
&ReturnResourcesOnThread,
|
||||
resource_collection_->GetReturnResourcesCallbackForImplThread(),
|
||||
returned_resources,
|
||||
&event));
|
||||
|
||||
run_loop.Run();
|
||||
}
|
||||
EXPECT_TRUE(ReturnAndResetResourcesAvailable());
|
||||
EXPECT_EQ(1u, returned_resources_.size());
|
||||
EXPECT_EQ(444u, returned_resources_[0].id);
|
||||
EXPECT_EQ(1, returned_resources_[0].count);
|
||||
returned_resources_.clear();
|
||||
|
||||
// The event prevents the return resources callback from being deleted.
|
||||
// Destroy the last reference from this thread to the collection before
|
||||
// signaling the event, to ensure any reference taken by the callback, if any,
|
||||
// would be the last one.
|
||||
DestroyResourceCollection();
|
||||
event.Signal();
|
||||
|
||||
CreateResourceCollection();
|
||||
resource_collection_->ReceivedResources(resources);
|
||||
resource_collection_->RefResources(resources);
|
||||
|
||||
// Destroy the collection before we have a chance to run the return callback.
|
||||
ReturnCallback return_callback =
|
||||
resource_collection_->GetReturnResourcesCallbackForImplThread();
|
||||
resource_collection_->LoseAllResources();
|
||||
DestroyResourceCollection();
|
||||
|
||||
EXPECT_TRUE(ReturnAndResetResourcesAvailable());
|
||||
EXPECT_EQ(1u, returned_resources_.size());
|
||||
EXPECT_EQ(444u, returned_resources_[0].id);
|
||||
EXPECT_EQ(1, returned_resources_[0].count);
|
||||
EXPECT_TRUE(returned_resources_[0].lost);
|
||||
returned_resources_.clear();
|
||||
|
||||
thread.message_loop()->PostTask(FROM_HERE,
|
||||
base::Bind(&ReturnResourcesOnThread,
|
||||
return_callback,
|
||||
returned_resources,
|
||||
&event));
|
||||
event.Signal();
|
||||
|
||||
thread.Stop();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace cc
|
Reference in New Issue
Block a user