| // Copyright (c) 2012 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 <stdint.h> |
| |
| #include "base/bind.h" |
| #include "base/location.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/threading/simple_thread.h" |
| #include "ppapi/c/pp_completion_callback.h" |
| #include "ppapi/c/pp_errors.h" |
| #include "ppapi/proxy/ppapi_proxy_test.h" |
| #include "ppapi/proxy/ppb_message_loop_proxy.h" |
| #include "ppapi/shared_impl/callback_tracker.h" |
| #include "ppapi/shared_impl/proxy_lock.h" |
| #include "ppapi/shared_impl/resource.h" |
| #include "ppapi/shared_impl/resource_tracker.h" |
| #include "ppapi/shared_impl/scoped_pp_resource.h" |
| #include "ppapi/shared_impl/test_globals.h" |
| #include "ppapi/shared_impl/tracked_callback.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| // Note, this file tests TrackedCallback which lives in ppapi/shared_impl. |
| // Unfortunately, we need the test to live in ppapi/proxy so that it can use |
| // the thread support there. |
| namespace ppapi { |
| namespace proxy { |
| |
| namespace { |
| |
| class CallbackThread : public base::SimpleThread { |
| public: |
| explicit CallbackThread(PP_Instance instance) |
| : SimpleThread("CallbackThread"), instance_(instance) {} |
| ~CallbackThread() override {} |
| |
| // base::SimpleThread overrides. |
| void BeforeStart() override { |
| ProxyAutoLock acquire; |
| // Create the message loop here, after PpapiGlobals has been created. |
| message_loop_ = new MessageLoopResource(instance_); |
| } |
| void BeforeJoin() override { |
| ProxyAutoLock acquire; |
| message_loop()->PostQuit(PP_TRUE); |
| message_loop_ = nullptr; |
| } |
| void Run() override { |
| ProxyAutoLock acquire; |
| // Make a local copy of message_loop_ for this thread so we can interact |
| // with it even after the main thread releases it. |
| scoped_refptr<MessageLoopResource> message_loop(message_loop_); |
| message_loop->AttachToCurrentThread(); |
| // Note, run releases the lock to run events. |
| base::RunLoop().Run(); |
| message_loop->DetachFromThread(); |
| } |
| |
| MessageLoopResource* message_loop() { return message_loop_.get(); } |
| |
| private: |
| PP_Instance instance_; |
| scoped_refptr<MessageLoopResource> message_loop_; |
| }; |
| |
| class TrackedCallbackTest : public PluginProxyTest { |
| public: |
| TrackedCallbackTest() : thread_(pp_instance()) {} |
| CallbackThread& thread() { return thread_; } |
| |
| private: |
| // PluginProxyTest overrides. |
| void SetUp() override { |
| PluginProxyTest::SetUp(); |
| thread_.Start(); |
| } |
| void TearDown() override { |
| thread_.Join(); |
| PluginProxyTest::TearDown(); |
| base::RunLoop run_loop; |
| run_loop.RunUntilIdle(); |
| } |
| CallbackThread thread_; |
| }; |
| |
| // All valid results (PP_OK, PP_ERROR_...) are nonpositive. |
| const int32_t kInitializedResultValue = 1; |
| const int32_t kOverrideResultValue = 2; |
| |
| struct CallbackRunInfo { |
| explicit CallbackRunInfo(base::ThreadChecker* thread_checker) |
| : run_count_(0), |
| result_(kInitializedResultValue), |
| completion_task_run_count_(0), |
| completion_task_result_(kInitializedResultValue), |
| thread_checker_(thread_checker), |
| callback_did_run_event_( |
| base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED) {} |
| void CallbackDidRun(int32_t result) { |
| CHECK(thread_checker_->CalledOnValidThread()); |
| if (!run_count_) |
| result_ = result; |
| ++run_count_; |
| callback_did_run_event_.Signal(); |
| } |
| void CompletionTaskDidRun(int32_t result) { |
| CHECK(thread_checker_->CalledOnValidThread()); |
| if (!completion_task_run_count_) |
| completion_task_result_ = result; |
| ++completion_task_run_count_; |
| } |
| void WaitUntilCompleted() { callback_did_run_event_.Wait(); } |
| unsigned run_count() { return run_count_; } |
| int32_t result() { return result_; } |
| unsigned completion_task_run_count() { return completion_task_run_count_; } |
| int32_t completion_task_result() { return completion_task_result_; } |
| private: |
| unsigned run_count_; |
| int32_t result_; |
| unsigned completion_task_run_count_; |
| int32_t completion_task_result_; |
| // Weak; owned by the creator of CallbackRunInfo. |
| base::ThreadChecker* thread_checker_; |
| |
| base::WaitableEvent callback_did_run_event_; |
| }; |
| |
| void TestCallback(void* user_data, int32_t result) { |
| CallbackRunInfo* info = static_cast<CallbackRunInfo*>(user_data); |
| info->CallbackDidRun(result); |
| } |
| |
| // CallbackShutdownTest -------------------------------------------------------- |
| |
| class CallbackShutdownTest : public TrackedCallbackTest { |
| public: |
| CallbackShutdownTest() : info_did_run_(&thread_checker_), |
| info_did_abort_(&thread_checker_), |
| info_didnt_run_(&thread_checker_) {} |
| |
| // 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: |
| base::ThreadChecker thread_checker_; |
| 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, DISABLED_AbortOnShutdown) { |
| ProxyAutoLock lock; |
| scoped_refptr<Resource> resource( |
| new Resource(OBJECT_IS_PROXY, pp_instance())); |
| |
| // Set up case (1) (see above). |
| EXPECT_EQ(0U, info_did_run().run_count()); |
| // TODO(dmichael): Test this on a background thread? |
| 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()); |
| |
| GetGlobals()->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: |
| static scoped_refptr<CallbackMockResource> Create(PP_Instance instance) { |
| ProxyAutoLock acquire; |
| return scoped_refptr<CallbackMockResource>( |
| new CallbackMockResource(instance)); |
| } |
| ~CallbackMockResource() {} |
| |
| // Take a reference to this resource, which will add it to the tracker. |
| void TakeRef() { |
| ProxyAutoLock acquire; |
| ScopedPPResource temp_resource(ScopedPPResource::PassRef(), GetReference()); |
| EXPECT_NE(0, temp_resource.get()); |
| reference_holder_ = temp_resource; |
| } |
| // Release it, removing it from the tracker. |
| void ReleaseRef() { |
| ProxyAutoLock acquire; |
| reference_holder_ = 0; |
| } |
| |
| // Create the test callbacks on a background thread, so that we can verify |
| // they are run on the same thread where they were created. |
| void CreateCallbacksOnLoop(MessageLoopResource* loop_resource) { |
| ProxyAutoLock acquire; |
| // |thread_checker_| will bind to the background thread. |
| thread_checker_.DetachFromThread(); |
| loop_resource->task_runner()->PostTask( |
| FROM_HERE, RunWhileLocked(base::BindOnce( |
| &CallbackMockResource::CreateCallbacks, this))); |
| } |
| |
| int32_t CompletionTask(CallbackRunInfo* info, int32_t result) { |
| // The completion task must run on the thread where the callback was |
| // created, and must hold the proxy lock. |
| CHECK(thread_checker_.CalledOnValidThread()); |
| ProxyLock::AssertAcquired(); |
| |
| // We should run before the callback. |
| CHECK_EQ(0U, info->run_count()); |
| info->CompletionTaskDidRun(result); |
| return kOverrideResultValue; |
| } |
| |
| void CheckInitialState() { |
| callbacks_created_event_.Wait(); |
| EXPECT_EQ(0U, info_did_run_.run_count()); |
| EXPECT_EQ(0U, info_did_run_.completion_task_run_count()); |
| |
| EXPECT_EQ(0U, info_did_run_with_completion_task_.run_count()); |
| EXPECT_EQ(0U, |
| info_did_run_with_completion_task_.completion_task_run_count()); |
| |
| EXPECT_EQ(0U, info_did_abort_.run_count()); |
| EXPECT_EQ(0U, info_did_abort_.completion_task_run_count()); |
| |
| EXPECT_EQ(0U, info_didnt_run_.run_count()); |
| EXPECT_EQ(0U, info_didnt_run_.completion_task_run_count()); |
| } |
| |
| void RunCallbacks() { |
| callback_did_run_->Run(PP_OK); |
| callback_did_run_with_completion_task_->Run(PP_OK); |
| callback_did_abort_->Abort(); |
| info_did_run_.WaitUntilCompleted(); |
| info_did_run_with_completion_task_.WaitUntilCompleted(); |
| info_did_abort_.WaitUntilCompleted(); |
| } |
| |
| void CheckIntermediateState() { |
| EXPECT_EQ(1U, info_did_run_.run_count()); |
| EXPECT_EQ(PP_OK, info_did_run_.result()); |
| EXPECT_EQ(0U, info_did_run_.completion_task_run_count()); |
| |
| EXPECT_EQ(1U, info_did_run_with_completion_task_.run_count()); |
| // completion task should override the result. |
| EXPECT_EQ(kOverrideResultValue, |
| info_did_run_with_completion_task_.result()); |
| EXPECT_EQ(1U, |
| info_did_run_with_completion_task_.completion_task_run_count()); |
| EXPECT_EQ(PP_OK, |
| info_did_run_with_completion_task_.completion_task_result()); |
| |
| EXPECT_EQ(1U, info_did_abort_.run_count()); |
| // completion task shouldn't override an abort. |
| EXPECT_EQ(PP_ERROR_ABORTED, info_did_abort_.result()); |
| EXPECT_EQ(1U, info_did_abort_.completion_task_run_count()); |
| EXPECT_EQ(PP_ERROR_ABORTED, info_did_abort_.completion_task_result()); |
| |
| EXPECT_EQ(0U, info_didnt_run_.completion_task_run_count()); |
| EXPECT_EQ(0U, info_didnt_run_.run_count()); |
| } |
| |
| void CheckFinalState() { |
| info_didnt_run_.WaitUntilCompleted(); |
| EXPECT_EQ(1U, info_did_run_with_completion_task_.run_count()); |
| EXPECT_EQ(kOverrideResultValue, |
| info_did_run_with_completion_task_.result()); |
| callback_did_run_with_completion_task_ = nullptr; |
| EXPECT_EQ(1U, info_did_run_.run_count()); |
| EXPECT_EQ(PP_OK, info_did_run_.result()); |
| callback_did_run_ = nullptr; |
| EXPECT_EQ(1U, info_did_abort_.run_count()); |
| EXPECT_EQ(PP_ERROR_ABORTED, info_did_abort_.result()); |
| callback_did_abort_ = nullptr; |
| EXPECT_EQ(1U, info_didnt_run_.run_count()); |
| EXPECT_EQ(PP_ERROR_ABORTED, info_didnt_run_.result()); |
| callback_didnt_run_ = nullptr; |
| } |
| |
| private: |
| explicit CallbackMockResource(PP_Instance instance) |
| : Resource(OBJECT_IS_PROXY, instance), |
| info_did_run_(&thread_checker_), |
| info_did_run_with_completion_task_(&thread_checker_), |
| info_did_abort_(&thread_checker_), |
| info_didnt_run_(&thread_checker_), |
| callbacks_created_event_( |
| base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED) {} |
| void CreateCallbacks() { |
| // Bind thread_checker_ to the thread where we create the callbacks. |
| // Later, when the callback runs, it will check that it was invoked on this |
| // same thread. |
| CHECK(thread_checker_.CalledOnValidThread()); |
| |
| callback_did_run_ = new TrackedCallback( |
| this, PP_MakeCompletionCallback(&TestCallback, &info_did_run_)); |
| |
| // In order to test that the completion task can override the callback |
| // result, we need to test callbacks with and without a completion task. |
| callback_did_run_with_completion_task_ = new TrackedCallback( |
| this, |
| PP_MakeCompletionCallback(&TestCallback, |
| &info_did_run_with_completion_task_)); |
| callback_did_run_with_completion_task_->set_completion_task( |
| Bind(&CallbackMockResource::CompletionTask, |
| this, |
| &info_did_run_with_completion_task_)); |
| |
| callback_did_abort_ = new TrackedCallback( |
| this, PP_MakeCompletionCallback(&TestCallback, &info_did_abort_)); |
| callback_did_abort_->set_completion_task( |
| Bind(&CallbackMockResource::CompletionTask, this, &info_did_abort_)); |
| |
| callback_didnt_run_ = new TrackedCallback( |
| this, PP_MakeCompletionCallback(&TestCallback, &info_didnt_run_)); |
| callback_didnt_run_->set_completion_task( |
| Bind(&CallbackMockResource::CompletionTask, this, &info_didnt_run_)); |
| |
| callbacks_created_event_.Signal(); |
| } |
| |
| // Used to verify that the callback runs on the same thread where it is |
| // created. |
| base::ThreadChecker thread_checker_; |
| |
| scoped_refptr<TrackedCallback> callback_did_run_; |
| CallbackRunInfo info_did_run_; |
| |
| scoped_refptr<TrackedCallback> callback_did_run_with_completion_task_; |
| CallbackRunInfo info_did_run_with_completion_task_; |
| |
| scoped_refptr<TrackedCallback> callback_did_abort_; |
| CallbackRunInfo info_did_abort_; |
| |
| scoped_refptr<TrackedCallback> callback_didnt_run_; |
| CallbackRunInfo info_didnt_run_; |
| |
| base::WaitableEvent callbacks_created_event_; |
| |
| ScopedPPResource reference_holder_; |
| }; |
| |
| } // namespace |
| |
| // Test that callbacks get aborted on the last resource unref. |
| TEST_F(CallbackResourceTest, DISABLED_AbortOnNoRef) { |
| // 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( |
| CallbackMockResource::Create(pp_instance())); |
| resource_1->CreateCallbacksOnLoop(thread().message_loop()); |
| resource_1->CheckInitialState(); |
| resource_1->RunCallbacks(); |
| resource_1->TakeRef(); |
| resource_1->CheckIntermediateState(); |
| |
| // 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( |
| CallbackMockResource::Create(pp_instance())); |
| resource_2->CreateCallbacksOnLoop(thread().message_loop()); |
| resource_2->CheckInitialState(); |
| resource_2->RunCallbacks(); |
| resource_2->TakeRef(); |
| resource_2->CheckIntermediateState(); |
| |
| // 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_1->ReleaseRef(); |
| |
| resource_1->CheckFinalState(); |
| resource_2->CheckIntermediateState(); |
| |
| // Kill resource #2. |
| resource_2->ReleaseRef(); |
| |
| resource_1->CheckFinalState(); |
| resource_2->CheckFinalState(); |
| |
| { |
| ProxyAutoLock lock; |
| resource_1 = nullptr; |
| resource_2 = nullptr; |
| } |
| } |
| |
| // Test that "resurrecting" a resource (getting a new ID for a |Resource|) |
| // doesn't resurrect callbacks. |
| TEST_F(CallbackResourceTest, DISABLED_Resurrection) { |
| scoped_refptr<CallbackMockResource> resource( |
| CallbackMockResource::Create(pp_instance())); |
| resource->CreateCallbacksOnLoop(thread().message_loop()); |
| resource->CheckInitialState(); |
| resource->RunCallbacks(); |
| resource->TakeRef(); |
| resource->CheckIntermediateState(); |
| |
| // Unref it and check that things are in the expected states. |
| resource->ReleaseRef(); |
| resource->CheckFinalState(); |
| |
| // "Resurrect" it and check that the callbacks are still dead. |
| resource->TakeRef(); |
| resource->CheckFinalState(); |
| |
| // Unref it again and do the same. |
| resource->ReleaseRef(); |
| resource->CheckFinalState(); |
| { |
| ProxyAutoLock lock; |
| resource = nullptr; |
| } |
| } |
| |
| } // namespace proxy |
| } // namespace ppapi |