diff options
author | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-29 20:16:50 +0000 |
---|---|---|
committer | brettw@chromium.org <brettw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-29 20:16:50 +0000 |
commit | bbc4912ddf6663cf88a469a2f271786829a273db (patch) | |
tree | 63fc2e6e9506c29c42a9ca8a10c87a4172746305 /ppapi | |
parent | d0b4d95e895bf5ac395c5f677c1fcab84229c3de (diff) | |
download | chromium_src-bbc4912ddf6663cf88a469a2f271786829a273db.zip chromium_src-bbc4912ddf6663cf88a469a2f271786829a273db.tar.gz chromium_src-bbc4912ddf6663cf88a469a2f271786829a273db.tar.bz2 |
Move the tracked completion callback code into shared impl.
This keeps the original which is still used by all existing callers. The new
one exists in parallel through the magic of namespaces. I'll convert the
existing callers over in a subsequent pass.
I split it out into separate files: tracked_callback and callback_tracker. I
also merged the old TrackedCallback and TrackedCompletionCallback since we
never have any callbascks that aren't completion callbacks.
BUG=74062
Review URL: http://codereview.chromium.org/9006055
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@115982 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ppapi')
-rw-r--r-- | ppapi/ppapi_shared.gypi | 4 | ||||
-rw-r--r-- | ppapi/ppapi_tests.gypi | 1 | ||||
-rw-r--r-- | ppapi/proxy/plugin_globals.cc | 10 | ||||
-rw-r--r-- | ppapi/proxy/plugin_globals.h | 4 | ||||
-rw-r--r-- | ppapi/shared_impl/callback_tracker.cc | 75 | ||||
-rw-r--r-- | ppapi/shared_impl/callback_tracker.h | 101 | ||||
-rw-r--r-- | ppapi/shared_impl/ppapi_globals.h | 3 | ||||
-rw-r--r-- | ppapi/shared_impl/resource_tracker.cc | 4 | ||||
-rw-r--r-- | ppapi/shared_impl/test_globals.cc | 9 | ||||
-rw-r--r-- | ppapi/shared_impl/test_globals.h | 4 | ||||
-rw-r--r-- | ppapi/shared_impl/tracked_callback.cc | 87 | ||||
-rw-r--r-- | ppapi/shared_impl/tracked_callback.h | 106 | ||||
-rw-r--r-- | ppapi/shared_impl/tracked_callback_unittest.cc | 266 |
13 files changed, 672 insertions, 2 deletions
diff --git a/ppapi/ppapi_shared.gypi b/ppapi/ppapi_shared.gypi index f7a01d9..a497a63 100644 --- a/ppapi/ppapi_shared.gypi +++ b/ppapi/ppapi_shared.gypi @@ -30,6 +30,8 @@ '../base/base.gyp:base', ], 'sources': [ + 'shared_impl/callback_tracker.cc', + 'shared_impl/callback_tracker.h', 'shared_impl/file_type_conversion.cc', 'shared_impl/file_type_conversion.h', 'shared_impl/function_group_base.cc', @@ -89,6 +91,8 @@ 'shared_impl/scoped_pp_resource.h', 'shared_impl/time_conversion.cc', 'shared_impl/time_conversion.h', + 'shared_impl/tracked_callback.cc', + 'shared_impl/tracked_callback.h', 'shared_impl/var.cc', 'shared_impl/var.h', 'shared_impl/var_tracker.cc', diff --git a/ppapi/ppapi_tests.gypi b/ppapi/ppapi_tests.gypi index 14e3d3c..26d2527 100644 --- a/ppapi/ppapi_tests.gypi +++ b/ppapi/ppapi_tests.gypi @@ -152,6 +152,7 @@ 'shared_impl/resource_tracker_unittest.cc', 'shared_impl/test_globals.cc', 'shared_impl/test_globals.h', + 'shared_impl/tracked_callback_unittest.cc', 'shared_impl/var_tracker_unittest.cc', ], }, diff --git a/ppapi/proxy/plugin_globals.cc b/ppapi/proxy/plugin_globals.cc index e5acc4c0..664fbeb 100644 --- a/ppapi/proxy/plugin_globals.cc +++ b/ppapi/proxy/plugin_globals.cc @@ -13,7 +13,8 @@ PluginGlobals* PluginGlobals::plugin_globals_ = NULL; PluginGlobals::PluginGlobals() : ppapi::PpapiGlobals(), - plugin_proxy_delegate_(NULL) { + plugin_proxy_delegate_(NULL), + callback_tracker_(new CallbackTracker) { DCHECK(!plugin_globals_); plugin_globals_ = this; } @@ -31,6 +32,13 @@ VarTracker* PluginGlobals::GetVarTracker() { return &plugin_var_tracker_; } +CallbackTracker* PluginGlobals::GetCallbackTrackerForInstance( + PP_Instance instance) { + // In the plugin process, the callback tracker is always the same, regardless + // of the instance. + return callback_tracker_.get(); +} + FunctionGroupBase* PluginGlobals::GetFunctionAPI(PP_Instance inst, ApiID id) { PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(inst); if (dispatcher) diff --git a/ppapi/proxy/plugin_globals.h b/ppapi/proxy/plugin_globals.h index a76b7f0..ffcf01e 100644 --- a/ppapi/proxy/plugin_globals.h +++ b/ppapi/proxy/plugin_globals.h @@ -9,6 +9,7 @@ #include "ppapi/proxy/plugin_resource_tracker.h" #include "ppapi/proxy/plugin_var_tracker.h" #include "ppapi/proxy/ppapi_proxy_export.h" +#include "ppapi/shared_impl/callback_tracker.h" #include "ppapi/shared_impl/ppapi_globals.h" namespace ppapi { @@ -29,6 +30,8 @@ class PPAPI_PROXY_EXPORT PluginGlobals : public PpapiGlobals { // PpapiGlobals implementation. virtual ResourceTracker* GetResourceTracker() OVERRIDE; virtual VarTracker* GetVarTracker() OVERRIDE; + virtual CallbackTracker* GetCallbackTrackerForInstance( + PP_Instance instance) OVERRIDE; virtual FunctionGroupBase* GetFunctionAPI(PP_Instance inst, ApiID id) OVERRIDE; virtual PP_Module GetModuleForInstance(PP_Instance instance) OVERRIDE; @@ -55,6 +58,7 @@ class PPAPI_PROXY_EXPORT PluginGlobals : public PpapiGlobals { PluginProxyDelegate* plugin_proxy_delegate_; PluginResourceTracker plugin_resource_tracker_; PluginVarTracker plugin_var_tracker_; + scoped_refptr<CallbackTracker> callback_tracker_; DISALLOW_COPY_AND_ASSIGN(PluginGlobals); }; diff --git a/ppapi/shared_impl/callback_tracker.cc b/ppapi/shared_impl/callback_tracker.cc new file mode 100644 index 0000000..1f22119 --- /dev/null +++ b/ppapi/shared_impl/callback_tracker.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2010 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 "ppapi/shared_impl/callback_tracker.h" + +#include <algorithm> + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "ppapi/c/pp_completion_callback.h" +#include "ppapi/shared_impl/tracked_callback.h" + +namespace ppapi { + +// CallbackTracker ------------------------------------------------------------- + +CallbackTracker::CallbackTracker() { +} + +void CallbackTracker::AbortAll() { + // Iterate over a copy since |Abort()| calls |Remove()| (indirectly). + // TODO(viettrungluu): This obviously isn't so efficient. + CallbackSetMap pending_callbacks_copy = pending_callbacks_; + for (CallbackSetMap::iterator it1 = pending_callbacks_copy.begin(); + it1 != pending_callbacks_copy.end(); ++it1) { + for (CallbackSet::iterator it2 = it1->second.begin(); + it2 != it1->second.end(); ++it2) { + (*it2)->Abort(); + } + } +} + +void CallbackTracker::PostAbortForResource(PP_Resource resource_id) { + CHECK(resource_id != 0); + CallbackSetMap::iterator it1 = pending_callbacks_.find(resource_id); + if (it1 == pending_callbacks_.end()) + return; + for (CallbackSet::iterator it2 = it1->second.begin(); + it2 != it1->second.end(); ++it2) { + // Post the abort. + (*it2)->PostAbort(); + } +} + +CallbackTracker::~CallbackTracker() { + // All callbacks must be aborted before destruction. + CHECK_EQ(0u, pending_callbacks_.size()); +} + +void CallbackTracker::Add( + const scoped_refptr<TrackedCallback>& tracked_callback) { + PP_Resource resource_id = tracked_callback->resource_id(); + DCHECK(pending_callbacks_[resource_id].find(tracked_callback) == + pending_callbacks_[resource_id].end()); + pending_callbacks_[resource_id].insert(tracked_callback); +} + +void CallbackTracker::Remove( + const scoped_refptr<TrackedCallback>& tracked_callback) { + CallbackSetMap::iterator map_it = + pending_callbacks_.find(tracked_callback->resource_id()); + DCHECK(map_it != pending_callbacks_.end()); + CallbackSet::iterator it = map_it->second.find(tracked_callback); + DCHECK(it != map_it->second.end()); + map_it->second.erase(it); + + // If there are no pending callbacks left for this ID, get rid of the entry. + if (map_it->second.empty()) + pending_callbacks_.erase(map_it); +} + +} // namespace ppapi diff --git a/ppapi/shared_impl/callback_tracker.h b/ppapi/shared_impl/callback_tracker.h new file mode 100644 index 0000000..5e354fb --- /dev/null +++ b/ppapi/shared_impl/callback_tracker.h @@ -0,0 +1,101 @@ +// Copyright (c) 2011 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. + +#ifndef PPAPI_SHARED_IMPL_CALLBACK_TRACKER_H_ +#define PPAPI_SHARED_IMPL_CALLBACK_TRACKER_H_ + +#include <map> +#include <set> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/task.h" +#include "ppapi/c/pp_resource.h" +#include "ppapi/shared_impl/ppapi_shared_export.h" + +namespace ppapi { + +class TrackedCallback; + +// Pepper callbacks have the following semantics (unless otherwise specified; +// in particular, the below apply to all completion callbacks): +// - Callbacks are always run on the main thread. +// - Callbacks are always called from the main message loop. In particular, +// calling into Pepper will not result in the plugin being re-entered via a +// synchronously-run callback. +// - Each callback will be executed (a.k.a. completed) exactly once. +// - Each callback may be *aborted*, which means that it will be executed with +// result |PP_ERROR_ABORTED| (in the case of completion callbacks). +// - Before |PPP_ShutdownModule()| is called, every pending callback (for that +// module) will be aborted. +// - Callbacks are usually associated to a resource, whose "deletion" provides +// a "cancellation" (or abort) mechanism -- see below. +// - When a plugin releases its last reference to resource, all callbacks +// associated to that resource are aborted. Even if a non-abortive completion +// of such a callback had previously been scheduled (i.e., posted), that +// callback must now be aborted. The aborts should be scheduled immediately +// (upon the last reference being given up) and should not rely on anything +// else (e.g., a background task to complete or further action from the +// plugin). +// - Abortive completion gives no information about the status of the +// asynchronous operation: The operation may have not yet begun, may be in +// progress, or may be completed (successfully or not). In fact, the +// operation may still proceed after the callback has been aborted. +// - Any output data buffers provided to Pepper are associated with a resource. +// Once that resource is released, no subsequent writes to those buffers. (If +// background threads are set up to write into such buffers, the final +// release operation should not return into the plugin until it can +// guaranteed that those threads will no longer write into the buffers.) +// +// Thread-safety notes: +// Currently, everything should happen on the main thread. The objects are +// thread-safe ref-counted, so objects which live on different threads may keep +// references. Releasing a reference to |TrackedCallback| on a different thread +// (possibly causing destruction) is also okay. Otherwise, all methods should be +// called only from the main thread. + +// |CallbackTracker| tracks pending Pepper callbacks for a single module. It +// also tracks, for each resource ID, which callbacks are pending. When a +// callback is (just about to be) completed, it is removed from the tracker. We +// use |CallbackTracker| for two things: (1) to ensure that all callbacks are +// properly aborted before module shutdown, and (2) to ensure that all callbacks +// associated to a given resource are aborted when a plugin (module) releases +// its last reference to that resource. +class PPAPI_SHARED_EXPORT CallbackTracker + : public base::RefCountedThreadSafe<CallbackTracker> { + public: + CallbackTracker(); + + // Abort all callbacks (synchronously). + void AbortAll(); + + // Abort all callbacks associated to the given resource ID (which must be + // valid, i.e., nonzero) by posting a task (or tasks). + void PostAbortForResource(PP_Resource resource_id); + + private: + friend class base::RefCountedThreadSafe<CallbackTracker>; + ~CallbackTracker(); + + // |TrackedCallback| are expected to automatically add and + // remove themselves from their provided |CallbackTracker|. + friend class TrackedCallback; + void Add(const scoped_refptr<TrackedCallback>& tracked_callback); + void Remove(const scoped_refptr<TrackedCallback>& tracked_callback); + + // For each resource ID with a pending callback, store a set with its pending + // callbacks. (Resource ID 0 is used for callbacks not associated to a valid + // resource.) If a resource ID is re-used for another resource, there may be + // aborted callbacks corresponding to the original resource in that set; these + // will be removed when they are completed (abortively). + typedef std::set<scoped_refptr<TrackedCallback> > CallbackSet; + typedef std::map<PP_Resource, CallbackSet> CallbackSetMap; + CallbackSetMap pending_callbacks_; + + DISALLOW_COPY_AND_ASSIGN(CallbackTracker); +}; + +} // namespace ppapi + +#endif // PPAPI_SHARED_IMPL_CALLBACK_TRACKER_H_ diff --git a/ppapi/shared_impl/ppapi_globals.h b/ppapi/shared_impl/ppapi_globals.h index 56ec6bf..b3d7d55 100644 --- a/ppapi/shared_impl/ppapi_globals.h +++ b/ppapi/shared_impl/ppapi_globals.h @@ -13,6 +13,7 @@ namespace ppapi { +class CallbackTracker; class FunctionGroupBase; class ResourceTracker; class VarTracker; @@ -29,6 +30,8 @@ class PPAPI_SHARED_EXPORT PpapiGlobals { // Retrieves the corresponding tracker. virtual ResourceTracker* GetResourceTracker() = 0; virtual VarTracker* GetVarTracker() = 0; + virtual CallbackTracker* GetCallbackTrackerForInstance( + PP_Instance instance) = 0; // Returns the function object corresponding to the given ID, or NULL if // there isn't one. diff --git a/ppapi/shared_impl/resource_tracker.cc b/ppapi/shared_impl/resource_tracker.cc index 2b3cb9f..3b66db8 100644 --- a/ppapi/shared_impl/resource_tracker.cc +++ b/ppapi/shared_impl/resource_tracker.cc @@ -4,7 +4,9 @@ #include "ppapi/shared_impl/resource_tracker.h" +#include "ppapi/shared_impl/callback_tracker.h" #include "ppapi/shared_impl/id_assignment.h" +#include "ppapi/shared_impl/ppapi_globals.h" #include "ppapi/shared_impl/resource.h" namespace ppapi { @@ -168,6 +170,8 @@ void ResourceTracker::RemoveResource(Resource* object) { } void ResourceTracker::LastPluginRefWasDeleted(Resource* object) { + PpapiGlobals::Get()->GetCallbackTrackerForInstance(object->pp_instance())-> + PostAbortForResource(object->pp_resource()); object->LastPluginRefWasDeleted(); } diff --git a/ppapi/shared_impl/test_globals.cc b/ppapi/shared_impl/test_globals.cc index 624b85b..e870e5c 100644 --- a/ppapi/shared_impl/test_globals.cc +++ b/ppapi/shared_impl/test_globals.cc @@ -6,7 +6,9 @@ namespace ppapi { -TestGlobals::TestGlobals() : ppapi::PpapiGlobals() { +TestGlobals::TestGlobals() + : ppapi::PpapiGlobals(), + callback_tracker_(new CallbackTracker) { } TestGlobals::~TestGlobals() { @@ -20,6 +22,11 @@ VarTracker* TestGlobals::GetVarTracker() { return &var_tracker_; } +CallbackTracker* TestGlobals::GetCallbackTrackerForInstance( + PP_Instance instance) { + return callback_tracker_.get(); +} + FunctionGroupBase* TestGlobals::GetFunctionAPI(PP_Instance inst, ApiID id) { return NULL; } diff --git a/ppapi/shared_impl/test_globals.h b/ppapi/shared_impl/test_globals.h index 49f189f..9c58fd3 100644 --- a/ppapi/shared_impl/test_globals.h +++ b/ppapi/shared_impl/test_globals.h @@ -6,6 +6,7 @@ #define PPAPI_SHARED_IMPL_TEST_GLOBALS_H_ #include "base/compiler_specific.h" +#include "ppapi/shared_impl/callback_tracker.h" #include "ppapi/shared_impl/ppapi_globals.h" #include "ppapi/shared_impl/resource_tracker.h" #include "ppapi/shared_impl/var_tracker.h" @@ -31,6 +32,8 @@ class TestGlobals : public PpapiGlobals { // PpapiGlobals implementation. virtual ResourceTracker* GetResourceTracker() OVERRIDE; virtual VarTracker* GetVarTracker() OVERRIDE; + virtual CallbackTracker* GetCallbackTrackerForInstance( + PP_Instance instance) OVERRIDE; virtual FunctionGroupBase* GetFunctionAPI(PP_Instance inst, ApiID id) OVERRIDE; virtual PP_Module GetModuleForInstance(PP_Instance instance) OVERRIDE; @@ -38,6 +41,7 @@ class TestGlobals : public PpapiGlobals { private: ResourceTracker resource_tracker_; TestVarTracker var_tracker_; + scoped_refptr<CallbackTracker> callback_tracker_; DISALLOW_COPY_AND_ASSIGN(TestGlobals); }; diff --git a/ppapi/shared_impl/tracked_callback.cc b/ppapi/shared_impl/tracked_callback.cc new file mode 100644 index 0000000..b36569d --- /dev/null +++ b/ppapi/shared_impl/tracked_callback.cc @@ -0,0 +1,87 @@ +// Copyright (c) 2010 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 "ppapi/shared_impl/tracked_callback.h" + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "ppapi/c/pp_completion_callback.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/shared_impl/callback_tracker.h" +#include "ppapi/shared_impl/ppapi_globals.h" +#include "ppapi/shared_impl/resource.h" + +namespace ppapi { + +// TrackedCallback ------------------------------------------------------------- + +// Note: don't keep a Resource* since it may go out of scope before us. +TrackedCallback::TrackedCallback( + Resource* resource, + const PP_CompletionCallback& callback) + : ALLOW_THIS_IN_INITIALIZER_LIST(abort_impl_factory_(this)), + resource_id_(resource->pp_resource()), + completed_(false), + aborted_(false), + callback_(callback) { + tracker_ = PpapiGlobals::Get()->GetCallbackTrackerForInstance( + resource->pp_instance()), + tracker_->Add(make_scoped_refptr(this)); +} + +TrackedCallback::~TrackedCallback() { +} + +void TrackedCallback::Abort() { + if (!completed()) { + aborted_ = true; + Run(PP_ERROR_ABORTED); + } +} + +void TrackedCallback::PostAbort() { + if (!completed()) { + aborted_ = true; + // Post a task for the abort (only if necessary). + if (!abort_impl_factory_.HasWeakPtrs()) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&TrackedCallback::Abort, + abort_impl_factory_.GetWeakPtr())); + } + } +} + +void TrackedCallback::Run(int32_t result) { + if (!completed()) { + // Cancel any pending calls. + abort_impl_factory_.InvalidateWeakPtrs(); + + // Copy |callback_| and look at |aborted()| now, since |MarkAsCompleted()| + // may delete us. + PP_CompletionCallback callback = callback_; + if (aborted()) + result = PP_ERROR_ABORTED; + + // Do this before running the callback in case of reentrancy (which + // shouldn't happen, but avoid strange failures). + MarkAsCompleted(); + PP_RunCompletionCallback(&callback, result); + } +} + +void TrackedCallback::MarkAsCompleted() { + DCHECK(!completed()); + + // We will be removed; maintain a reference to ensure we won't be deleted + // until we're done. + scoped_refptr<TrackedCallback> thiz = this; + completed_ = true; + tracker_->Remove(thiz); + tracker_ = NULL; +} + +} // namespace ppapi diff --git a/ppapi/shared_impl/tracked_callback.h b/ppapi/shared_impl/tracked_callback.h new file mode 100644 index 0000000..ed42a59 --- /dev/null +++ b/ppapi/shared_impl/tracked_callback.h @@ -0,0 +1,106 @@ +// Copyright (c) 2011 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. + +#ifndef PPAPI_SHARED_IMPL_TRACKED_CALLBACK_H_ +#define PPAPI_SHARED_IMPL_TRACKED_CALLBACK_H_ + +#include <map> +#include <set> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/task.h" +#include "ppapi/c/pp_completion_callback.h" +#include "ppapi/c/pp_resource.h" +#include "ppapi/shared_impl/ppapi_shared_export.h" + +namespace ppapi { + +class CallbackTracker; +class Resource; + +// |TrackedCallback| represents a tracked Pepper callback (from the browser to +// the plugin), typically still pending. Such callbacks have the standard Pepper +// callback semantics. Execution (i.e., completion) of callbacks happens through +// objects of subclasses of |TrackedCallback|. Two things are ensured: (1) that +// the callback is executed at most once, and (2) once a callback is marked to +// be aborted, any subsequent completion is abortive (even if a non-abortive +// completion had previously been scheduled). +// +// The details of non-abortive completion depend on the type of callback (e.g., +// different parameters may be required), but basic abort functionality is core. +// The ability to post aborts is needed in many situations to ensure that the +// plugin is not re-entered into. (Note that posting a task to just run +// |Abort()| is different and not correct; calling |PostAbort()| additionally +// guarantees that all subsequent completions will be abortive.) +// +// This class is reference counted so that different things can hang on to it, +// and not worry too much about ensuring Pepper callback semantics. Note that +// the "owning" |CallbackTracker| will keep a reference until the callback is +// completed. +// +// Subclasses must do several things: +// - They must ensure that the callback is executed at most once (by looking at +// |completed()| before running the callback). +// - They must ensure that the callback is run abortively if it is marked as to +// be aborted (by looking at |aborted()| before running the callback). +// - They must call |MarkAsCompleted()| immediately before actually running the +// callback; see the comment for |MarkAsCompleted()| for a caveat. +class PPAPI_SHARED_EXPORT TrackedCallback + : public base::RefCountedThreadSafe<TrackedCallback> { + public: + // Create a tracked completion callback and register it with the tracker. The + // resource pointer is not stored. + TrackedCallback(Resource* resource, + const PP_CompletionCallback& callback); + + // These run the callback in an abortive manner, or post a task to do so (but + // immediately marking the callback as to be aborted). + void Abort(); + void PostAbort(); + + // Run the callback with the given result. If the callback had previously been + // marked as to be aborted (by |PostAbort()|), |result| will be ignored and + // the callback will be run with result |PP_ERROR_ABORTED|. + void Run(int32_t result); + + // Returns the ID of the resource which "owns" the callback, or 0 if the + // callback is not associated with any resource. + PP_Resource resource_id() const { return resource_id_; } + + // Returns true if the callback was completed (possibly aborted). + bool completed() const { return completed_; } + + // Returns true if the callback was or should be aborted; this will be the + // case whenever |Abort()| or |PostAbort()| is called before a non-abortive + // completion. + bool aborted() const { return aborted_; } + + private: + // This class is ref counted. + friend class base::RefCountedThreadSafe<TrackedCallback>; + virtual ~TrackedCallback(); + + // Mark this object as complete and remove it from the tracker. This must only + // be called once. Note that running this may result in this object being + // deleted (so keep a reference if it'll still be needed). + void MarkAsCompleted(); + + // Factory used by |PostAbort()|. Note that it's safe to cancel any pending + // posted aborts on destruction -- before it's destroyed, the "owning" + // |CallbackTracker| must have gone through and done (synchronous) |Abort()|s. + base::WeakPtrFactory<TrackedCallback> abort_impl_factory_; + + scoped_refptr<CallbackTracker> tracker_; + PP_Resource resource_id_; + bool completed_; + bool aborted_; + PP_CompletionCallback callback_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(TrackedCallback); +}; + +} // namespace ppapi + +#endif // PPAPI_SHARED_IMPL_TRACKED_CALLBACK_H_ diff --git a/ppapi/shared_impl/tracked_callback_unittest.cc b/ppapi/shared_impl/tracked_callback_unittest.cc new file mode 100644 index 0000000..62da396 --- /dev/null +++ b/ppapi/shared_impl/tracked_callback_unittest.cc @@ -0,0 +1,266 @@ +// Copyright (c) 2011 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/memory/ref_counted.h" +#include "base/message_loop.h" +#include "ppapi/c/pp_completion_callback.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/shared_impl/callback_tracker.h" +#include "ppapi/shared_impl/resource.h" +#include "ppapi/shared_impl/resource_tracker.h" +#include "ppapi/shared_impl/test_globals.h" +#include "ppapi/shared_impl/tracked_callback.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace ppapi { + +namespace { + +class TrackedCallbackTest : public testing::Test { + public: + TrackedCallbackTest() + : message_loop_(MessageLoop::TYPE_DEFAULT), + pp_instance_(1234) {} + + PP_Instance pp_instance() const { return pp_instance_; } + + virtual void SetUp() OVERRIDE { + globals_.GetResourceTracker()->DidCreateInstance(pp_instance_); + } + virtual void TearDown() OVERRIDE { + globals_.GetResourceTracker()->DidDeleteInstance(pp_instance_); + } + + private: + MessageLoop message_loop_; + TestGlobals globals_; + PP_Instance pp_instance_; +}; + +struct CallbackRunInfo { + // All valid results (PP_OK, PP_ERROR_...) are nonpositive. + CallbackRunInfo() : run_count(0), result(1) {} + unsigned run_count; + int32_t result; +}; + +void TestCallback(void* user_data, int32_t result) { + CallbackRunInfo* info = reinterpret_cast<CallbackRunInfo*>(user_data); + info->run_count++; + if (info->run_count == 1) + info->result = result; +} + +} // namespace + +// CallbackShutdownTest -------------------------------------------------------- + +namespace { + +class CallbackShutdownTest : public TrackedCallbackTest { + public: + CallbackShutdownTest() {} + + // Cases: + // (1) A callback which is run (so shouldn't be aborted on shutdown). + // (2) A callback which is aborted (so shouldn't be aborted on shutdown). + // (3) A callback which isn't run (so should be aborted on shutdown). + CallbackRunInfo& info_did_run() { return info_did_run_; } // (1) + CallbackRunInfo& info_did_abort() { return info_did_abort_; } // (2) + CallbackRunInfo& info_didnt_run() { return info_didnt_run_; } // (3) + + private: + CallbackRunInfo info_did_run_; + CallbackRunInfo info_did_abort_; + CallbackRunInfo info_didnt_run_; +}; + +} // namespace + +// Tests that callbacks are properly aborted on module shutdown. +TEST_F(CallbackShutdownTest, AbortOnShutdown) { + scoped_refptr<Resource> resource(new Resource(pp_instance())); + + // Set up case (1) (see above). + EXPECT_EQ(0U, info_did_run().run_count); + scoped_refptr<TrackedCallback> callback_did_run = new TrackedCallback( + resource.get(), + PP_MakeCompletionCallback(&TestCallback, &info_did_run())); + EXPECT_EQ(0U, info_did_run().run_count); + callback_did_run->Run(PP_OK); + EXPECT_EQ(1U, info_did_run().run_count); + EXPECT_EQ(PP_OK, info_did_run().result); + + // Set up case (2). + EXPECT_EQ(0U, info_did_abort().run_count); + scoped_refptr<TrackedCallback> callback_did_abort = new TrackedCallback( + resource.get(), + PP_MakeCompletionCallback(&TestCallback, &info_did_abort())); + EXPECT_EQ(0U, info_did_abort().run_count); + callback_did_abort->Abort(); + EXPECT_EQ(1U, info_did_abort().run_count); + EXPECT_EQ(PP_ERROR_ABORTED, info_did_abort().result); + + // Set up case (3). + EXPECT_EQ(0U, info_didnt_run().run_count); + scoped_refptr<TrackedCallback> callback_didnt_run = new TrackedCallback( + resource.get(), + PP_MakeCompletionCallback(&TestCallback, &info_didnt_run())); + EXPECT_EQ(0U, info_didnt_run().run_count); + + PpapiGlobals::Get()->GetCallbackTrackerForInstance(pp_instance())->AbortAll(); + + // Check case (1). + EXPECT_EQ(1U, info_did_run().run_count); + + // Check case (2). + EXPECT_EQ(1U, info_did_abort().run_count); + + // Check case (3). + EXPECT_EQ(1U, info_didnt_run().run_count); + EXPECT_EQ(PP_ERROR_ABORTED, info_didnt_run().result); +} + +// CallbackResourceTest -------------------------------------------------------- + +namespace { + +class CallbackResourceTest : public TrackedCallbackTest { + public: + CallbackResourceTest() {} +}; + +class CallbackMockResource : public Resource { + public: + CallbackMockResource(PP_Instance instance) : Resource(instance) {} + ~CallbackMockResource() {} + + PP_Resource SetupForTest() { + PP_Resource resource_id = GetReference(); + EXPECT_NE(0, resource_id); + + callback_did_run_ = new TrackedCallback( + this, + PP_MakeCompletionCallback(&TestCallback, &info_did_run_)); + EXPECT_EQ(0U, info_did_run_.run_count); + + callback_did_abort_ = new TrackedCallback( + this, + PP_MakeCompletionCallback(&TestCallback, &info_did_abort_)); + EXPECT_EQ(0U, info_did_abort_.run_count); + + callback_didnt_run_ = new TrackedCallback( + this, + PP_MakeCompletionCallback(&TestCallback, &info_didnt_run_)); + EXPECT_EQ(0U, info_didnt_run_.run_count); + + callback_did_run_->Run(PP_OK); + callback_did_abort_->Abort(); + + CheckIntermediateState(); + + return resource_id; + } + + void CheckIntermediateState() { + EXPECT_EQ(1U, info_did_run_.run_count); + EXPECT_EQ(PP_OK, info_did_run_.result); + + EXPECT_EQ(1U, info_did_abort_.run_count); + EXPECT_EQ(PP_ERROR_ABORTED, info_did_abort_.result); + + EXPECT_EQ(0U, info_didnt_run_.run_count); + } + + void CheckFinalState() { + EXPECT_EQ(1U, info_did_run_.run_count); + EXPECT_EQ(PP_OK, info_did_run_.result); + EXPECT_EQ(1U, info_did_abort_.run_count); + EXPECT_EQ(PP_ERROR_ABORTED, info_did_abort_.result); + EXPECT_EQ(1U, info_didnt_run_.run_count); + EXPECT_EQ(PP_ERROR_ABORTED, info_didnt_run_.result); + } + + scoped_refptr<TrackedCallback> callback_did_run_; + CallbackRunInfo info_did_run_; + + scoped_refptr<TrackedCallback> callback_did_abort_; + CallbackRunInfo info_did_abort_; + + scoped_refptr<TrackedCallback> callback_didnt_run_; + CallbackRunInfo info_didnt_run_; +}; + +} // namespace + +// Test that callbacks get aborted on the last resource unref. +TEST_F(CallbackResourceTest, AbortOnNoRef) { + ResourceTracker* resource_tracker = + PpapiGlobals::Get()->GetResourceTracker(); + + // Test several things: Unref-ing a resource (to zero refs) with callbacks + // which (1) have been run, (2) have been aborted, (3) haven't been completed. + // Check that the uncompleted one gets aborted, and that the others don't get + // called again. + scoped_refptr<CallbackMockResource> resource_1( + new CallbackMockResource(pp_instance())); + PP_Resource resource_1_id = resource_1->SetupForTest(); + + // Also do the same for a second resource, and make sure that unref-ing the + // first resource doesn't much up the second resource. + scoped_refptr<CallbackMockResource> resource_2( + new CallbackMockResource(pp_instance())); + PP_Resource resource_2_id = resource_2->SetupForTest(); + + // Double-check that resource #1 is still okay. + resource_1->CheckIntermediateState(); + + // Kill resource #1, spin the message loop to run posted calls, and check that + // things are in the expected states. + resource_tracker->ReleaseResource(resource_1_id); + MessageLoop::current()->RunAllPending(); + resource_1->CheckFinalState(); + resource_2->CheckIntermediateState(); + + // Kill resource #2. + resource_tracker->ReleaseResource(resource_2_id); + MessageLoop::current()->RunAllPending(); + resource_1->CheckFinalState(); + resource_2->CheckFinalState(); + + // This shouldn't be needed, but make sure there are no stranded tasks. + MessageLoop::current()->RunAllPending(); +} + +// Test that "resurrecting" a resource (getting a new ID for a |Resource|) +// doesn't resurrect callbacks. +TEST_F(CallbackResourceTest, Resurrection) { + ResourceTracker* resource_tracker = + PpapiGlobals::Get()->GetResourceTracker(); + + scoped_refptr<CallbackMockResource> resource( + new CallbackMockResource(pp_instance())); + PP_Resource resource_id = resource->SetupForTest(); + + // Unref it, spin the message loop to run posted calls, and check that things + // are in the expected states. + resource_tracker->ReleaseResource(resource_id); + MessageLoop::current()->RunAllPending(); + resource->CheckFinalState(); + + // "Resurrect" it and check that the callbacks are still dead. + PP_Resource new_resource_id = resource->GetReference(); + MessageLoop::current()->RunAllPending(); + resource->CheckFinalState(); + + // Unref it again and do the same. + resource_tracker->ReleaseResource(new_resource_id); + MessageLoop::current()->RunAllPending(); + resource->CheckFinalState(); + + // This shouldn't be needed, but make sure there are no stranded tasks. + MessageLoop::current()->RunAllPending(); +} + +} // namespace ppapi |