]>
git.proxmox.com Git - ceph.git/blob - ceph/src/common/async/completion.h
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 * Ceph - scalable distributed file system
6 * Copyright (C) 2018 Red Hat
8 * This is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License version 2.1, as published by the Free Software
11 * Foundation. See file COPYING.
15 #ifndef CEPH_ASYNC_COMPLETION_H
16 #define CEPH_ASYNC_COMPLETION_H
20 #include "bind_handler.h"
21 #include "forward_handler.h"
23 namespace ceph::async
{
26 * Abstract completion handler interface for use with boost::asio.
28 * Memory management is performed using the Handler's 'associated allocator',
29 * which carries the additional requirement that its memory be released before
30 * the Handler is invoked. This allows memory allocated for one asynchronous
31 * operation to be reused in its continuation. Because of this requirement, any
32 * calls to invoke the completion must first release ownership of it. To enforce
33 * this, the static functions defer()/dispatch()/post() take the completion by
34 * rvalue-reference to std::unique_ptr<Completion>, i.e. std::move(completion).
36 * Handlers may also have an 'associated executor', so the calls to defer(),
37 * dispatch(), and post() are forwarded to that executor. If there is no
38 * associated executor (which is generally the case unless one was bound with
39 * boost::asio::bind_executor()), the executor passed to Completion::create()
40 * is used as a default.
44 * // declare a Completion type with Signature = void(int, string)
45 * using MyCompletion = ceph::async::Completion<void(int, string)>;
47 * // create a completion with the given callback:
48 * std::unique_ptr<MyCompletion> c;
49 * c = MyCompletion::create(ex, [] (int a, const string& b) {});
51 * // bind arguments to the callback and post to its associated executor:
52 * MyCompletion::post(std::move(c), 5, "hello");
55 * Additional user data may be stored along with the Completion to take
56 * advantage of the handler allocator optimization. This is accomplished by
57 * specifying its type in the template parameter T. For example, the type
58 * Completion<void(), int> contains a public member variable 'int user_data'.
59 * Any additional arguments to Completion::create() will be forwarded to type
62 * If the AsBase<T> type tag is used, as in Completion<void(), AsBase<T>>,
63 * the Completion will inherit from T instead of declaring it as a member
66 * When invoking the completion handler via defer(), dispatch(), or post(),
67 * care must be taken when passing arguments that refer to user data, because
68 * its memory is destroyed prior to invocation. In such cases, the user data
69 * should be moved/copied out of the Completion first.
71 template <typename Signature
, typename T
= void>
75 /// type tag for UserData
76 template <typename T
> struct AsBase
{};
80 /// optional user data to be stored with the Completion
84 template <typename
...Args
>
85 UserData(Args
&& ...args
)
86 : user_data(std::forward
<Args
>(args
)...)
89 // AsBase specialization inherits from T
91 struct UserData
<AsBase
<T
>> : public T
{
92 template <typename
...Args
>
93 UserData(Args
&& ...args
)
94 : T(std::forward
<Args
>(args
)...)
97 // void specialization
99 class UserData
<void> {};
101 } // namespace detail
104 // template specialization to pull the Signature's args apart
105 template <typename T
, typename
...Args
>
106 class Completion
<void(Args
...), T
> : public detail::UserData
<T
> {
108 // internal interfaces for type-erasure on the Handler/Executor. uses
109 // tuple<Args...> to provide perfect forwarding because you can't make
110 // virtual function templates
111 virtual void destroy_defer(std::tuple
<Args
...>&& args
) = 0;
112 virtual void destroy_dispatch(std::tuple
<Args
...>&& args
) = 0;
113 virtual void destroy_post(std::tuple
<Args
...>&& args
) = 0;
114 virtual void destroy() = 0;
116 // constructor is protected, use create(). any constructor arguments are
117 // forwarded to UserData
118 template <typename
...TArgs
>
119 Completion(TArgs
&& ...args
)
120 : detail::UserData
<T
>(std::forward
<TArgs
>(args
)...)
123 virtual ~Completion() = default;
125 // use the virtual destroy() interface on delete. this allows the derived
126 // class to manage its memory using Handler allocators, without having to use
127 // a custom Deleter for std::unique_ptr<>
128 static void operator delete(void *p
) {
129 static_cast<Completion
*>(p
)->destroy();
132 /// completion factory function that uses the handler's associated allocator.
133 /// any additional arguments are forwared to T's constructor
134 template <typename Executor1
, typename Handler
, typename
...TArgs
>
135 static std::unique_ptr
<Completion
>
136 create(const Executor1
& ex1
, Handler
&& handler
, TArgs
&& ...args
);
138 /// take ownership of the completion, bind any arguments to the completion
139 /// handler, then defer() it on its associated executor
140 template <typename
...Args2
>
141 static void defer(std::unique_ptr
<Completion
>&& c
, Args2
&&...args
);
143 /// take ownership of the completion, bind any arguments to the completion
144 /// handler, then dispatch() it on its associated executor
145 template <typename
...Args2
>
146 static void dispatch(std::unique_ptr
<Completion
>&& c
, Args2
&&...args
);
148 /// take ownership of the completion, bind any arguments to the completion
149 /// handler, then post() it to its associated executor
150 template <typename
...Args2
>
151 static void post(std::unique_ptr
<Completion
>&& c
, Args2
&&...args
);
156 // concrete Completion that knows how to invoke the completion handler. this
157 // observes all of the 'Requirements on asynchronous operations' specified by
158 // the C++ Networking TS
159 template <typename Executor1
, typename Handler
, typename T
, typename
...Args
>
160 class CompletionImpl final
: public Completion
<void(Args
...), T
> {
161 // use Handler's associated executor (or Executor1 by default) for callbacks
162 using Executor2
= boost::asio::associated_executor_t
<Handler
, Executor1
>;
163 // maintain work on both executors
164 using Work1
= boost::asio::executor_work_guard
<Executor1
>;
165 using Work2
= boost::asio::executor_work_guard
<Executor2
>;
166 std::pair
<Work1
, Work2
> work
;
169 // use Handler's associated allocator
170 using Alloc2
= boost::asio::associated_allocator_t
<Handler
>;
171 using Traits2
= std::allocator_traits
<Alloc2
>;
172 using RebindAlloc2
= typename
Traits2::template rebind_alloc
<CompletionImpl
>;
173 using RebindTraits2
= std::allocator_traits
<RebindAlloc2
>;
175 // placement new for the handler allocator
176 static void* operator new(size_t, RebindAlloc2 alloc2
) {
177 return RebindTraits2::allocate(alloc2
, 1);
179 // placement delete for when the constructor throws during placement new
180 static void operator delete(void *p
, RebindAlloc2 alloc2
) {
181 RebindTraits2::deallocate(alloc2
, static_cast<CompletionImpl
*>(p
), 1);
184 static auto bind_and_forward(Handler
&& h
, std::tuple
<Args
...>&& args
) {
185 return forward_handler(CompletionHandler
{std::move(h
), std::move(args
)});
188 void destroy_defer(std::tuple
<Args
...>&& args
) override
{
189 auto w
= std::move(work
);
190 auto f
= bind_and_forward(std::move(handler
), std::move(args
));
191 RebindAlloc2 alloc2
= boost::asio::get_associated_allocator(handler
);
192 RebindTraits2::destroy(alloc2
, this);
193 RebindTraits2::deallocate(alloc2
, this, 1);
194 w
.second
.get_executor().defer(std::move(f
), alloc2
);
196 void destroy_dispatch(std::tuple
<Args
...>&& args
) override
{
197 auto w
= std::move(work
);
198 auto f
= bind_and_forward(std::move(handler
), std::move(args
));
199 RebindAlloc2 alloc2
= boost::asio::get_associated_allocator(handler
);
200 RebindTraits2::destroy(alloc2
, this);
201 RebindTraits2::deallocate(alloc2
, this, 1);
202 w
.second
.get_executor().dispatch(std::move(f
), alloc2
);
204 void destroy_post(std::tuple
<Args
...>&& args
) override
{
205 auto w
= std::move(work
);
206 auto f
= bind_and_forward(std::move(handler
), std::move(args
));
207 RebindAlloc2 alloc2
= boost::asio::get_associated_allocator(handler
);
208 RebindTraits2::destroy(alloc2
, this);
209 RebindTraits2::deallocate(alloc2
, this, 1);
210 w
.second
.get_executor().post(std::move(f
), alloc2
);
212 void destroy() override
{
213 RebindAlloc2 alloc2
= boost::asio::get_associated_allocator(handler
);
214 RebindTraits2::destroy(alloc2
, this);
215 RebindTraits2::deallocate(alloc2
, this, 1);
218 // constructor is private, use create(). extra constructor arguments are
219 // forwarded to UserData
220 template <typename
...TArgs
>
221 CompletionImpl(const Executor1
& ex1
, Handler
&& handler
, TArgs
&& ...args
)
222 : Completion
<void(Args
...), T
>(std::forward
<TArgs
>(args
)...),
223 work(ex1
, boost::asio::make_work_guard(handler
, ex1
)),
224 handler(std::move(handler
))
228 template <typename
...TArgs
>
229 static auto create(const Executor1
& ex
, Handler
&& handler
, TArgs
&& ...args
) {
230 auto alloc2
= boost::asio::get_associated_allocator(handler
);
231 using Ptr
= std::unique_ptr
<CompletionImpl
>;
232 return Ptr
{new (alloc2
) CompletionImpl(ex
, std::move(handler
),
233 std::forward
<TArgs
>(args
)...)};
236 static void operator delete(void *p
) {
237 static_cast<CompletionImpl
*>(p
)->destroy();
241 } // namespace detail
244 template <typename T
, typename
...Args
>
245 template <typename Executor1
, typename Handler
, typename
...TArgs
>
246 std::unique_ptr
<Completion
<void(Args
...), T
>>
247 Completion
<void(Args
...), T
>::create(const Executor1
& ex
,
248 Handler
&& handler
, TArgs
&& ...args
)
250 using Impl
= detail::CompletionImpl
<Executor1
, Handler
, T
, Args
...>;
251 return Impl::create(ex
, std::forward
<Handler
>(handler
),
252 std::forward
<TArgs
>(args
)...);
255 template <typename T
, typename
...Args
>
256 template <typename
...Args2
>
257 void Completion
<void(Args
...), T
>::defer(std::unique_ptr
<Completion
>&& ptr
,
260 auto c
= ptr
.release();
261 c
->destroy_defer(std::make_tuple(std::forward
<Args2
>(args
)...));
264 template <typename T
, typename
...Args
>
265 template <typename
...Args2
>
266 void Completion
<void(Args
...), T
>::dispatch(std::unique_ptr
<Completion
>&& ptr
,
269 auto c
= ptr
.release();
270 c
->destroy_dispatch(std::make_tuple(std::forward
<Args2
>(args
)...));
273 template <typename T
, typename
...Args
>
274 template <typename
...Args2
>
275 void Completion
<void(Args
...), T
>::post(std::unique_ptr
<Completion
>&& ptr
,
278 auto c
= ptr
.release();
279 c
->destroy_post(std::make_tuple(std::forward
<Args2
>(args
)...));
283 /// completion factory function that uses the handler's associated allocator.
284 /// any additional arguments are forwared to T's constructor
285 template <typename Signature
, typename T
, typename Executor1
,
286 typename Handler
, typename
...TArgs
>
287 std::unique_ptr
<Completion
<Signature
, T
>>
288 create_completion(const Executor1
& ex
, Handler
&& handler
, TArgs
&& ...args
)
290 return Completion
<Signature
, T
>::create(ex
, std::forward
<Handler
>(handler
),
291 std::forward
<TArgs
>(args
)...);
294 /// take ownership of the completion, bind any arguments to the completion
295 /// handler, then defer() it on its associated executor
296 template <typename Signature
, typename T
, typename
...Args
>
297 void defer(std::unique_ptr
<Completion
<Signature
, T
>>&& ptr
, Args
&& ...args
)
299 Completion
<Signature
, T
>::defer(std::move(ptr
), std::forward
<Args
>(args
)...);
302 /// take ownership of the completion, bind any arguments to the completion
303 /// handler, then dispatch() it on its associated executor
304 template <typename Signature
, typename T
, typename
...Args
>
305 void dispatch(std::unique_ptr
<Completion
<Signature
, T
>>&& ptr
, Args
&& ...args
)
307 Completion
<Signature
, T
>::dispatch(std::move(ptr
), std::forward
<Args
>(args
)...);
310 /// take ownership of the completion, bind any arguments to the completion
311 /// handler, then post() it to its associated executor
312 template <typename Signature
, typename T
, typename
...Args
>
313 void post(std::unique_ptr
<Completion
<Signature
, T
>>&& ptr
, Args
&& ...args
)
315 Completion
<Signature
, T
>::post(std::move(ptr
), std::forward
<Args
>(args
)...);
318 } // namespace ceph::async
320 #endif // CEPH_ASYNC_COMPLETION_H