// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/threading/post_task_and_reply_impl.h"

#include <utility>

#include "base/check_op.h"
#include "base/debug/leak_annotations.h"
#include "base/functional/bind.h"
#include "base/memory/ref_counted.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool/thread_pool_instance.h"

namespace base {

namespace {

class PostTaskAndReplyRelay {
 public:
  PostTaskAndReplyRelay(const Location& from_here,
                        OnceClosure task,
                        OnceClosure reply,
                        scoped_refptr<SequencedTaskRunner> reply_task_runner)
      : from_here_(from_here),
        task_(std::move(task)),
        reply_(std::move(reply)),
        reply_task_runner_(std::move(reply_task_runner)) {}
  PostTaskAndReplyRelay(PostTaskAndReplyRelay&&) = default;

  PostTaskAndReplyRelay(const PostTaskAndReplyRelay&) = delete;
  PostTaskAndReplyRelay& operator=(const PostTaskAndReplyRelay&) = delete;

  // It is important that |reply_| always be deleted on the origin sequence
  // (|reply_task_runner_|) since its destructor can be affine to it. More
  // sutbly, it is also important that |task_| be destroyed on the origin
  // sequence when it fails to run. This is because |task_| can own state which
  // is affine to |reply_task_runner_| and was intended to be handed to
  // |reply_|, e.g. https://crbug.com/829122. Since |task_| already needs to
  // support deletion on the origin sequence (since the initial PostTask can
  // always fail), it's safer to delete it there when PostTask succeeds but
  // |task_| is later prevented from running.
  //
  // PostTaskAndReplyRelay's move semantics along with logic in this destructor
  // enforce the above semantics in all the following cases :
  //  1) Posting |task_| fails right away on the origin sequence:
  //    a) |reply_task_runner_| is null (i.e. during late shutdown);
  //    b) |reply_task_runner_| is set.
  //  2) ~PostTaskAndReplyRelay() runs on the destination sequence:
  //    a) RunTaskAndPostReply() is cancelled before running;
  //    b) RunTaskAndPostReply() is skipped on shutdown;
  //    c) Posting RunReply() fails.
  //  3) ~PostTaskAndReplyRelay() runs on the origin sequence:
  //    a) RunReply() is cancelled before running;
  //    b) RunReply() is skipped on shutdown;
  //    c) The DeleteSoon() posted by (2) runs.
  //  4) ~PostTaskAndReplyRelay() should no-op:
  //    a) This relay was moved to another relay instance;
  //    b) RunReply() ran and completed this relay's mandate.
  ~PostTaskAndReplyRelay() {
    // Case 1a and 4a:
    if (!reply_task_runner_) {
      DCHECK_EQ(task_.is_null(), reply_.is_null());
      return;
    }

    // Case 4b:
    if (!reply_) {
      DCHECK(!task_);
      return;
    }

    // Case 2:
    if (!reply_task_runner_->RunsTasksInCurrentSequence()) {
      DCHECK(reply_);
      // Allow this task to be leaked on shutdown even if `reply_task_runner_`
      // has the TaskShutdownBehaviour::BLOCK_SHUTDOWN trait. Without `fizzler`,
      // such a task runner would DCHECK when posting to `reply_task_runner_`
      // after shutdown. Ignore this DCHECK as the poster isn't in control when
      // its Callback is destroyed late into shutdown. Ref. crbug.com/1375270.
      base::ThreadPoolInstance::ScopedFizzleBlockShutdownTasks fizzler;

      SequencedTaskRunner* reply_task_runner_raw = reply_task_runner_.get();
      auto relay_to_delete =
          std::make_unique<PostTaskAndReplyRelay>(std::move(*this));
      // In case 2c, posting the DeleteSoon will also fail and |relay_to_delete|
      // will be leaked. This only happens during shutdown and leaking is better
      // than thread-unsafe execution.
      ANNOTATE_LEAKING_OBJECT_PTR(relay_to_delete.get());
      reply_task_runner_raw->DeleteSoon(from_here_, std::move(relay_to_delete));
      return;
    }

    // Case 1b and 3: Any remaining state will be destroyed synchronously at the
    // end of this scope.
  }

  // No assignment operator because of const member.
  PostTaskAndReplyRelay& operator=(PostTaskAndReplyRelay&&) = delete;

  // Static function is used because it is not possible to bind a method call to
  // a non-pointer type.
  static void RunTaskAndPostReply(PostTaskAndReplyRelay relay) {
    DCHECK(relay.task_);
    std::move(relay.task_).Run();

    // Keep a pointer to the reply TaskRunner for the PostTask() call before
    // |relay| is moved into a callback.
    SequencedTaskRunner* reply_task_runner_raw = relay.reply_task_runner_.get();

    const Location from_here = relay.from_here_;
    reply_task_runner_raw->PostTask(
        from_here,
        BindOnce(&PostTaskAndReplyRelay::RunReply, std::move(relay)));
  }

 private:
  // Static function is used because it is not possible to bind a method call to
  // a non-pointer type.
  static void RunReply(PostTaskAndReplyRelay relay) {
    DCHECK(!relay.task_);
    DCHECK(relay.reply_);
    std::move(relay.reply_).Run();
  }

  const Location from_here_;
  OnceClosure task_;
  OnceClosure reply_;
  // Not const to allow moving.
  scoped_refptr<SequencedTaskRunner> reply_task_runner_;
};

}  // namespace

namespace internal {

bool PostTaskAndReplyImpl::PostTaskAndReply(const Location& from_here,
                                            OnceClosure task,
                                            OnceClosure reply) {
  DCHECK(task) << from_here.ToString();
  DCHECK(reply) << from_here.ToString();

  const bool has_sequenced_context = SequencedTaskRunner::HasCurrentDefault();

  const bool post_task_success = PostTask(
      from_here, BindOnce(&PostTaskAndReplyRelay::RunTaskAndPostReply,
                          PostTaskAndReplyRelay(
                              from_here, std::move(task), std::move(reply),
                              has_sequenced_context
                                  ? SequencedTaskRunner::GetCurrentDefault()
                                  : nullptr)));

  // PostTaskAndReply() requires a SequencedTaskRunner::CurrentDefaultHandle to
  // post the reply.  Having no SequencedTaskRunner::CurrentDefaultHandle is
  // allowed when posting the task fails, to simplify calls during shutdown
  // (https://crbug.com/922938).
  CHECK(has_sequenced_context || !post_task_success);

  return post_task_success;
}

}  // namespace internal

}  // namespace base