| // 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 |