// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab /* * Ceph - scalable distributed file system * * Copyright (C) 2018 Red Hat * * This is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1, as published by the Free Software * Foundation. See file COPYING. * */ #ifndef CEPH_ASYNC_COMPLETION_H #define CEPH_ASYNC_COMPLETION_H #include #include "bind_handler.h" #include "forward_handler.h" namespace ceph::async { /** * Abstract completion handler interface for use with boost::asio. * * Memory management is performed using the Handler's 'associated allocator', * which carries the additional requirement that its memory be released before * the Handler is invoked. This allows memory allocated for one asynchronous * operation to be reused in its continuation. Because of this requirement, any * calls to invoke the completion must first release ownership of it. To enforce * this, the static functions defer()/dispatch()/post() take the completion by * rvalue-reference to std::unique_ptr, i.e. std::move(completion). * * Handlers may also have an 'associated executor', so the calls to defer(), * dispatch(), and post() are forwarded to that executor. If there is no * associated executor (which is generally the case unless one was bound with * boost::asio::bind_executor()), the executor passed to Completion::create() * is used as a default. * * Example use: * * // declare a Completion type with Signature = void(int, string) * using MyCompletion = ceph::async::Completion; * * // create a completion with the given callback: * std::unique_ptr c; * c = MyCompletion::create(ex, [] (int a, const string& b) {}); * * // bind arguments to the callback and post to its associated executor: * MyCompletion::post(std::move(c), 5, "hello"); * * * Additional user data may be stored along with the Completion to take * advantage of the handler allocator optimization. This is accomplished by * specifying its type in the template parameter T. For example, the type * Completion contains a public member variable 'int user_data'. * Any additional arguments to Completion::create() will be forwarded to type * T's constructor. * * If the AsBase type tag is used, as in Completion>, * the Completion will inherit from T instead of declaring it as a member * variable. * * When invoking the completion handler via defer(), dispatch(), or post(), * care must be taken when passing arguments that refer to user data, because * its memory is destroyed prior to invocation. In such cases, the user data * should be moved/copied out of the Completion first. */ template class Completion; /// type tag for UserData template struct AsBase {}; namespace detail { /// optional user data to be stored with the Completion template struct UserData { T user_data; template UserData(Args&& ...args) : user_data(std::forward(args)...) {} }; // AsBase specialization inherits from T template struct UserData> : public T { template UserData(Args&& ...args) : T(std::forward(args)...) {} }; // void specialization template <> class UserData {}; } // namespace detail // template specialization to pull the Signature's args apart template class Completion : public detail::UserData { protected: // internal interfaces for type-erasure on the Handler/Executor. uses // tuple to provide perfect forwarding because you can't make // virtual function templates virtual void destroy_defer(std::tuple&& args) = 0; virtual void destroy_dispatch(std::tuple&& args) = 0; virtual void destroy_post(std::tuple&& args) = 0; virtual void destroy() = 0; // constructor is protected, use create(). any constructor arguments are // forwarded to UserData template Completion(TArgs&& ...args) : detail::UserData(std::forward(args)...) {} public: virtual ~Completion() = default; // use the virtual destroy() interface on delete. this allows the derived // class to manage its memory using Handler allocators, without having to use // a custom Deleter for std::unique_ptr<> static void operator delete(void *p) { static_cast(p)->destroy(); } /// completion factory function that uses the handler's associated allocator. /// any additional arguments are forwared to T's constructor template static std::unique_ptr create(const Executor1& ex1, Handler&& handler, TArgs&& ...args); /// take ownership of the completion, bind any arguments to the completion /// handler, then defer() it on its associated executor template static void defer(std::unique_ptr&& c, Args2&&...args); /// take ownership of the completion, bind any arguments to the completion /// handler, then dispatch() it on its associated executor template static void dispatch(std::unique_ptr&& c, Args2&&...args); /// take ownership of the completion, bind any arguments to the completion /// handler, then post() it to its associated executor template static void post(std::unique_ptr&& c, Args2&&...args); }; namespace detail { // concrete Completion that knows how to invoke the completion handler. this // observes all of the 'Requirements on asynchronous operations' specified by // the C++ Networking TS template class CompletionImpl final : public Completion { // use Handler's associated executor (or Executor1 by default) for callbacks using Executor2 = boost::asio::associated_executor_t; // maintain work on both executors using Work1 = boost::asio::executor_work_guard; using Work2 = boost::asio::executor_work_guard; std::pair work; Handler handler; // use Handler's associated allocator using Alloc2 = boost::asio::associated_allocator_t; using Traits2 = std::allocator_traits; using RebindAlloc2 = typename Traits2::template rebind_alloc; using RebindTraits2 = std::allocator_traits; // placement new for the handler allocator static void* operator new(size_t, RebindAlloc2 alloc2) { return RebindTraits2::allocate(alloc2, 1); } // placement delete for when the constructor throws during placement new static void operator delete(void *p, RebindAlloc2 alloc2) { RebindTraits2::deallocate(alloc2, static_cast(p), 1); } static auto bind_and_forward(Handler&& h, std::tuple&& args) { return forward_handler(CompletionHandler{std::move(h), std::move(args)}); } void destroy_defer(std::tuple&& args) override { auto w = std::move(work); auto f = bind_and_forward(std::move(handler), std::move(args)); RebindAlloc2 alloc2 = boost::asio::get_associated_allocator(handler); RebindTraits2::destroy(alloc2, this); RebindTraits2::deallocate(alloc2, this, 1); w.second.get_executor().defer(std::move(f), alloc2); } void destroy_dispatch(std::tuple&& args) override { auto w = std::move(work); auto f = bind_and_forward(std::move(handler), std::move(args)); RebindAlloc2 alloc2 = boost::asio::get_associated_allocator(handler); RebindTraits2::destroy(alloc2, this); RebindTraits2::deallocate(alloc2, this, 1); w.second.get_executor().dispatch(std::move(f), alloc2); } void destroy_post(std::tuple&& args) override { auto w = std::move(work); auto f = bind_and_forward(std::move(handler), std::move(args)); RebindAlloc2 alloc2 = boost::asio::get_associated_allocator(handler); RebindTraits2::destroy(alloc2, this); RebindTraits2::deallocate(alloc2, this, 1); w.second.get_executor().post(std::move(f), alloc2); } void destroy() override { RebindAlloc2 alloc2 = boost::asio::get_associated_allocator(handler); RebindTraits2::destroy(alloc2, this); RebindTraits2::deallocate(alloc2, this, 1); } // constructor is private, use create(). extra constructor arguments are // forwarded to UserData template CompletionImpl(const Executor1& ex1, Handler&& handler, TArgs&& ...args) : Completion(std::forward(args)...), work(ex1, boost::asio::make_work_guard(handler, ex1)), handler(std::move(handler)) {} public: template static auto create(const Executor1& ex, Handler&& handler, TArgs&& ...args) { auto alloc2 = boost::asio::get_associated_allocator(handler); using Ptr = std::unique_ptr; return Ptr{new (alloc2) CompletionImpl(ex, std::move(handler), std::forward(args)...)}; } static void operator delete(void *p) { static_cast(p)->destroy(); } }; } // namespace detail template template std::unique_ptr> Completion::create(const Executor1& ex, Handler&& handler, TArgs&& ...args) { using Impl = detail::CompletionImpl; return Impl::create(ex, std::forward(handler), std::forward(args)...); } template template void Completion::defer(std::unique_ptr&& ptr, Args2&& ...args) { auto c = ptr.release(); c->destroy_defer(std::make_tuple(std::forward(args)...)); } template template void Completion::dispatch(std::unique_ptr&& ptr, Args2&& ...args) { auto c = ptr.release(); c->destroy_dispatch(std::make_tuple(std::forward(args)...)); } template template void Completion::post(std::unique_ptr&& ptr, Args2&& ...args) { auto c = ptr.release(); c->destroy_post(std::make_tuple(std::forward(args)...)); } /// completion factory function that uses the handler's associated allocator. /// any additional arguments are forwared to T's constructor template std::unique_ptr> create_completion(const Executor1& ex, Handler&& handler, TArgs&& ...args) { return Completion::create(ex, std::forward(handler), std::forward(args)...); } /// take ownership of the completion, bind any arguments to the completion /// handler, then defer() it on its associated executor template void defer(std::unique_ptr>&& ptr, Args&& ...args) { Completion::defer(std::move(ptr), std::forward(args)...); } /// take ownership of the completion, bind any arguments to the completion /// handler, then dispatch() it on its associated executor template void dispatch(std::unique_ptr>&& ptr, Args&& ...args) { Completion::dispatch(std::move(ptr), std::forward(args)...); } /// take ownership of the completion, bind any arguments to the completion /// handler, then post() it to its associated executor template void post(std::unique_ptr>&& ptr, Args&& ...args) { Completion::post(std::move(ptr), std::forward(args)...); } } // namespace ceph::async #endif // CEPH_ASYNC_COMPLETION_H