chromium/ppapi/proxy/tracked_callback_unittest.cc

// Copyright 2012 The Chromium Authors
// 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/functional/bind.h"
#include "base/location.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/single_thread_task_runner.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(
        base::BindOnce(&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(base::BindOnce(
        &CallbackMockResource::CompletionTask, this, &info_did_abort_));

    callback_didnt_run_ = new TrackedCallback(
        this, PP_MakeCompletionCallback(&TestCallback, &info_didnt_run_));
    callback_didnt_run_->set_completion_task(base::BindOnce(
        &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