| // Copyright 2019 The Chromium OS 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 LIBHWSEC_TASK_DISPATCHING_FRAMEWORK_H_ |
| #define LIBHWSEC_TASK_DISPATCHING_FRAMEWORK_H_ |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/location.h> |
| #include <base/threading/thread_task_runner_handle.h> |
| #include <brillo/dbus/dbus_method_response.h> |
| |
| namespace hwsec { |
| |
| // This class allows DBusMethodResponse to Return(), ReplyWithError() or |
| // destruct from any thread. However, it should be noted that the creation of |
| // this class should be on the original dbus thread, and this class does not |
| // handle the situation whereby Return() or ReplyWithError() is called from two |
| // different threads. (It is the task of the caller to ensure that each instance |
| // returns only once.) |
| template <typename... Types> |
| class ThreadSafeDBusMethodResponse |
| : public brillo::dbus_utils::DBusMethodResponse<Types...> { |
| public: |
| using BaseClass = brillo::dbus_utils::DBusMethodResponse<Types...>; |
| using DBusMethodResponse = brillo::dbus_utils::DBusMethodResponse<Types...>; |
| |
| ThreadSafeDBusMethodResponse(ThreadSafeDBusMethodResponse&& callback) = |
| default; |
| explicit ThreadSafeDBusMethodResponse(BaseClass&& original_callback) |
| : BaseClass::DBusMethodResponse( |
| nullptr, |
| base::Bind([](std::unique_ptr<dbus::Response>) { NOTREACHED(); })), |
| origin_task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| origin_thread_id_(base::PlatformThread::CurrentId()), |
| original_callback_(new BaseClass(std::move(original_callback))) {} |
| |
| ~ThreadSafeDBusMethodResponse() override { |
| // The base class can only be destroyed on the original thread, |
| // because if this method haven't been sent, then it'll try to send an |
| // empty response, and that may only happen on the original thread. |
| // |
| // If we are not on the original thread, we move out the |
| // |original_callback_|. The callback will be destruct at original thread, |
| // and this class is safe to destruct in current thread. |
| if (!IsOnOriginalThread()) { |
| origin_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce([](const std::unique_ptr<BaseClass>& callback) {}, |
| std::move(original_callback_))); |
| } |
| } |
| |
| void Return(const Types&... return_values) override { |
| if (IsOnOriginalThread()) { |
| original_callback_->Return(return_values...); |
| } else { |
| // We are not on the original thread, so we'll post it back |
| origin_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&BaseClass::Return, std::move(original_callback_), |
| return_values...)); |
| } |
| } |
| |
| void ReplyWithError(const brillo::Error* error) override { |
| if (IsOnOriginalThread()) { |
| original_callback_->ReplyWithError(error); |
| } else { |
| // We are not on the original thread, so we'll post it back. |
| origin_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce( |
| [](std::unique_ptr<BaseClass> callback, |
| std::unique_ptr<brillo::Error> error) { |
| callback->ReplyWithError(error.get()); |
| }, |
| std::move(original_callback_), error->Clone())); |
| } |
| } |
| |
| void ReplyWithError(const base::Location& location, |
| const std::string& error_domain, |
| const std::string& error_code, |
| const std::string& error_message) override { |
| if (IsOnOriginalThread()) { |
| original_callback_->ReplyWithError(location, error_domain, error_code, |
| error_message); |
| } else { |
| // We are not on the original thread, so we'll post it back. |
| origin_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| [](std::unique_ptr<BaseClass> original_callback, |
| const base::Location& location, |
| const std::string& error_domain, const std::string& error_code, |
| const std::string& error_message) { |
| original_callback->ReplyWithError(location, error_domain, |
| error_code, error_message); |
| }, |
| std::move(original_callback_), location, error_domain, error_code, |
| error_message)); |
| } |
| } |
| |
| static std::unique_ptr<DBusMethodResponse> MakeThreadSafe( |
| std::unique_ptr<DBusMethodResponse> response) { |
| return std::make_unique<ThreadSafeDBusMethodResponse>(std::move(*response)); |
| } |
| |
| private: |
| bool IsOnOriginalThread() const { |
| return base::PlatformThread::CurrentId() == origin_thread_id_; |
| } |
| |
| // We record the task runner and thread id from which this object is created |
| // so that when Reply(), ReplyWithError() is called, we can verify if it's on |
| // the original thread, if it's not, we can post it. |
| scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_; |
| base::PlatformThreadId origin_thread_id_; |
| |
| // The instatnce of base class. It is initialized at constructor. |
| // Because it should operate on the original thread, we will pass it to the |
| // original thread when needed, and it will deconstruct at the original thread |
| // when the task is compelete. By the design of original callback, this class |
| // is not designed to be called twice, and the caller should handle this. |
| std::unique_ptr<BaseClass> original_callback_; |
| }; |
| |
| } // namespace hwsec |
| |
| #endif // LIBHWSEC_TASK_DISPATCHING_FRAMEWORK_H_ |