Skip to content

Commit b0f2bb7

Browse files
committed
common/async: Add redirect_error.h
Straightforwardly update `redirect_error` to work with Asio's `disposition` concept generally. No point in sending upstream because Asio never accepts contributions. Signed-off-by: Adam C. Emerson <[email protected]>
1 parent 52c80fb commit b0f2bb7

File tree

4 files changed

+629
-0
lines changed

4 files changed

+629
-0
lines changed

COPYING

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,12 @@ Copyright: 2020 Red Hat <[email protected]>
211211
2003-2019 Christopher M. Kohlhoff <[email protected]>
212212
License: Boost Software License, Version 1.0
213213

214+
Files: src/common/async/redirect_error.h,
215+
src/test/common/test_async_redirect_error.cc
216+
Copyright: 2025 Contributors to the Ceph Project
217+
2003-2024 Christopher M. Kohlhoff <[email protected]>
218+
License: Boost Software License, Version 1.0
219+
214220
Files: src/script/backport-create-issue
215221
Copyright: 2015 Red Hat <[email protected]>
216222
2018 SUSE LLC

src/common/async/redirect_error.h

Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2+
// vim: ts=8 sw=2 smarttab ft=cpp
3+
/*
4+
* Ceph - scalable distributed file system
5+
*/
6+
7+
// Copyright: 2025 Contributors to the Ceph Project
8+
// Based on boost/asio/redirect_error.hpp and
9+
// boost/asio/impl/redirect_error.hpp which are
10+
// Copyright (c) 2003-2024 Christopher M. Kohlhoff (chris at kohlhoff dot com)
11+
//
12+
// Distributed under the Boost Software License, Version 1.0. (See
13+
// accompanying copy at http://www.boost.org/LICENSE_1_0.txt)
14+
15+
#pragma once
16+
17+
#include <type_traits>
18+
19+
#include <boost/asio/associated_executor.hpp>
20+
#include <boost/asio/async_result.hpp>
21+
#include <boost/asio/default_completion_token.hpp>
22+
#include <boost/asio/disposition.hpp>
23+
#include <boost/asio/handler_continuation_hook.hpp>
24+
25+
/// \file common/async/redirect_error.h
26+
///
27+
/// \brief `redirect_error` that knows about `dispositions`.
28+
///
29+
/// Asio has a very useful concept called a `disposition` that
30+
/// generalizes the notion of an error code. Unfortunately
31+
/// `redirect_error` doesn't know about dispositions, making it less
32+
/// useful.
33+
34+
namespace ceph::async {
35+
36+
/// A @ref completion_token adapter used to specify that an error
37+
/// produced by an asynchronous operation is captured to a specified
38+
/// variable. The variable must be of a `disposition` type.
39+
/**
40+
* The redirect_error_t class is used to indicate that any disposition produced
41+
* by an asynchronous operation is captured to a specified variable.
42+
*/
43+
template<typename CompletionToken,
44+
boost::asio::disposition Disposition>
45+
class redirect_error_t {
46+
public: // Sigh
47+
CompletionToken token;
48+
Disposition& disposition;
49+
50+
template<typename CT>
51+
redirect_error_t(CT&& token, Disposition& disposition)
52+
: token(std::forward<CT>(token)), disposition(disposition) {}
53+
};
54+
55+
/// A function object type that adapts a @ref completion_token to capture
56+
/// disposition values to a variable.
57+
/**
58+
* May also be used directly as a completion token, in which case it adapts the
59+
* asynchronous operation's default completion token (or boost::asio::deferred
60+
* if no default is available).
61+
*/
62+
template<boost::asio::disposition Disposition>
63+
class partial_redirect_error {
64+
public:
65+
Disposition& disposition;
66+
67+
/// Constructor that specifies the variable used to capture disposition values.
68+
explicit partial_redirect_error(Disposition& disposition)
69+
: disposition(disposition) {}
70+
71+
/// Adapt a @ref completion_token to specify that the completion handler
72+
/// should capture disposition values to a variable.
73+
template<typename CompletionToken>
74+
[[nodiscard]] constexpr inline auto
75+
operator ()(CompletionToken&& token) const {
76+
return redirect_error_t<std::decay_t<CompletionToken>, Disposition>{
77+
std::forward<CompletionToken>(token), disposition};
78+
}
79+
};
80+
81+
/// Create a partial completion token adapter that captures disposition values
82+
/// to a variable.
83+
template<boost::asio::disposition Disposition>
84+
[[nodiscard]] inline auto redirect_error(Disposition& d)
85+
{
86+
return partial_redirect_error<std::decay_t<Disposition>>{d};
87+
}
88+
89+
/// Adapt a @ref completion_token to capture disposition values to a variable.
90+
template<typename CompletionToken, boost::asio::disposition Disposition>
91+
[[nodiscard]] inline auto redirect_error(CompletionToken&& token,
92+
Disposition& d)
93+
{
94+
return redirect_error_t<std::decay_t<CompletionToken>,
95+
std::decay_t<Disposition>>{
96+
std::forward<CompletionToken>(token), d};
97+
}
98+
99+
namespace detail {
100+
template<typename Handler, boost::asio::disposition Disposition>
101+
class redirect_error_handler {
102+
public:
103+
Disposition& disposition;
104+
// Essentially a call-once function, invoked as an rvalue.
105+
Handler handler;
106+
107+
using result_type = void;
108+
template<typename CompletionToken>
109+
redirect_error_handler(
110+
redirect_error_t<std::decay_t<CompletionToken>,
111+
std::decay_t<Disposition>> re)
112+
: disposition(re.disposition), handler(std::move(re.token)) {}
113+
114+
template<typename RedirectedHandler>
115+
redirect_error_handler(Disposition &disposition, RedirectedHandler&& handler)
116+
: disposition(disposition),
117+
handler(std::forward<RedirectedHandler>(handler)) {}
118+
119+
120+
void operator ()() {
121+
std::move(handler)();
122+
}
123+
124+
template<typename Arg0, typename ...Args>
125+
std::enable_if_t<!std::is_same_v<std::decay_t<Arg0>,
126+
Disposition>>
127+
operator ()(Arg0&& arg0, Args ...args) {
128+
std::move(handler)(std::forward<Arg0>(arg0), std::forward<Args>(args)...);
129+
}
130+
131+
template<typename... Args>
132+
void operator ()(const Disposition& d, Args&& ...args) {
133+
disposition = d;
134+
std::move(handler)(std::forward<Args>(args)...);
135+
}
136+
};
137+
138+
template<boost::asio::disposition Disposition, typename Handler>
139+
inline bool asio_handler_is_continuation(
140+
redirect_error_handler<Disposition, Handler>* this_handler)
141+
{
142+
using boost::asio::asio_handler_is_continuation;
143+
return asio_handler_is_continuation(&this_handler->handler);
144+
}
145+
146+
template<typename Signature>
147+
struct redirect_error_signature
148+
{
149+
using type = Signature;
150+
};
151+
152+
template<typename R, boost::asio::disposition Disposition, typename... Args>
153+
struct redirect_error_signature<R(Disposition, Args...)>
154+
{
155+
typedef R type(Args...);
156+
};
157+
158+
template<typename R, boost::asio::disposition Disposition, typename... Args>
159+
struct redirect_error_signature<R(const Disposition&, Args...)>
160+
{
161+
typedef R type(Args...);
162+
};
163+
164+
template<typename R, boost::asio::disposition Disposition, typename... Args>
165+
struct redirect_error_signature<R(Disposition, Args...) &>
166+
{
167+
typedef R type(Args...) &;
168+
};
169+
170+
template<typename R, boost::asio::disposition Disposition, typename... Args>
171+
struct redirect_error_signature<R(const Disposition&, Args...) &>
172+
{
173+
typedef R type(Args...) &;
174+
};
175+
176+
template<typename R, boost::asio::disposition Disposition, typename... Args>
177+
struct redirect_error_signature<R(Disposition, Args...) &&>
178+
{
179+
typedef R type(Args...) &&;
180+
};
181+
182+
template<typename R, boost::asio::disposition Disposition, typename... Args>
183+
struct redirect_error_signature<R(const Disposition&, Args...) &&>
184+
{
185+
typedef R type(Args...) &&;
186+
};
187+
188+
template<typename R, boost::asio::disposition Disposition, typename... Args>
189+
struct redirect_error_signature<R(Disposition, Args...) noexcept>
190+
{
191+
typedef R type(Args...) & noexcept;
192+
};
193+
194+
template<typename R, boost::asio::disposition Disposition, typename... Args>
195+
struct redirect_error_signature<R(const Disposition&, Args...) noexcept>
196+
{
197+
typedef R type(Args...) & noexcept;
198+
};
199+
200+
template<typename R, boost::asio::disposition Disposition, typename... Args>
201+
struct redirect_error_signature<R(Disposition, Args...) & noexcept>
202+
{
203+
typedef R type(Args...) & noexcept;
204+
};
205+
206+
template <typename R, boost::asio::disposition Disposition, typename... Args>
207+
struct redirect_error_signature<R(const Disposition&, Args...) & noexcept>
208+
{
209+
typedef R type(Args...) & noexcept;
210+
};
211+
212+
template<typename R, boost::asio::disposition Disposition, typename... Args>
213+
struct redirect_error_signature<R(Disposition, Args...) && noexcept>
214+
{
215+
typedef R type(Args...) && noexcept;
216+
};
217+
218+
template <typename R, boost::asio::disposition Disposition, typename... Args>
219+
struct redirect_error_signature<R(const Disposition&, Args...) && noexcept>
220+
{
221+
typedef R type(Args...) && noexcept;
222+
};
223+
224+
template<typename Initiation, typename = void>
225+
class initiation_base : public Initiation
226+
{
227+
public:
228+
template<typename I>
229+
explicit initiation_base(I&& initiation)
230+
: Initiation(std::forward<I>(initiation)) {}
231+
};
232+
233+
template<typename Initiation>
234+
class initiation_base<Initiation,
235+
std::enable_if_t<!std::is_class_v<Initiation>>>
236+
{
237+
public:
238+
template<typename I>
239+
explicit initiation_base(I&& initiation)
240+
: initiation(std::forward<I>(initiation)) {}
241+
242+
template<typename... Args>
243+
void operator()(Args&&... args) const
244+
{
245+
initiation(std::forward<Args>(args)...);
246+
}
247+
248+
private:
249+
Initiation initiation;
250+
};
251+
} // namespace detail
252+
} // namespace ceph::async
253+
254+
namespace boost::asio {
255+
256+
template<boost::asio::disposition Disposition, typename CompletionToken,
257+
typename Signature>
258+
struct async_result<::ceph::async::redirect_error_t<CompletionToken,
259+
Disposition>, Signature>
260+
: async_result<CompletionToken,
261+
typename ::ceph::async::detail::redirect_error_signature<
262+
Signature>::type>
263+
{
264+
template<typename Initiation>
265+
struct init_wrapper : ::ceph::async::detail::initiation_base<Initiation>
266+
{
267+
using ::ceph::async::detail::initiation_base<Initiation>::initiation_base;
268+
269+
template<typename Handler, typename... Args>
270+
void operator ()(Handler&& handler, Disposition* d, Args&&... args) &&
271+
{
272+
static_cast<Initiation&&>(*this)(
273+
::ceph::async::detail::redirect_error_handler<
274+
decay_t<Handler>, Disposition>(
275+
*d, std::forward<Handler>(handler)),
276+
std::forward<Args>(args)...);
277+
}
278+
279+
template<typename Handler, typename... Args>
280+
void operator ()(Handler&& handler, Disposition* d, Args&&... args) const &
281+
{
282+
static_cast<const Initiation&>(*this)(
283+
::ceph::async::detail::redirect_error_handler<
284+
decay_t<Handler>, Disposition>(
285+
*d, std::forward<Handler>(handler)),
286+
std::forward<Args>(args)...);
287+
}
288+
};
289+
290+
template<typename Initiation, typename RawCompletionToken, typename... Args>
291+
static auto initiate(Initiation&& initiation,
292+
RawCompletionToken&& token, Args&&... args)
293+
{
294+
return async_initiate<
295+
std::conditional_t<
296+
std::is_const_v<remove_reference_t<RawCompletionToken>>,
297+
const CompletionToken, CompletionToken>,
298+
typename ::ceph::async::detail::redirect_error_signature<Signature>::type>(
299+
init_wrapper<std::decay_t<Initiation>>(
300+
std::forward<Initiation>(initiation)),
301+
token.token, &token.disposition, std::forward<Args>(args)...);
302+
}
303+
};
304+
305+
template<template<typename, typename> class Associator, typename Handler,
306+
typename DefaultCandidate, typename Disposition>
307+
struct associator<Associator,
308+
::ceph::async::detail::redirect_error_handler<Handler,
309+
Disposition>,
310+
DefaultCandidate>
311+
: Associator<Handler, DefaultCandidate>
312+
{
313+
static auto get(const ::ceph::async::detail::redirect_error_handler<
314+
Handler, Disposition>& h) noexcept
315+
{
316+
return Associator<Handler, DefaultCandidate>::get(h.handler);
317+
}
318+
319+
static auto get(const ::ceph::async::detail::redirect_error_handler<
320+
Handler, Disposition>& h,
321+
const DefaultCandidate& c) noexcept
322+
{
323+
return Associator<Handler, DefaultCandidate>::get(h.handler, c);
324+
}
325+
};
326+
327+
template<boost::asio::disposition Disposition, typename... Signatures>
328+
struct async_result<::ceph::async::partial_redirect_error<Disposition>,
329+
Signatures...>
330+
{
331+
template <typename Initiation, typename RawCompletionToken, typename... Args>
332+
static auto initiate(Initiation&& initiation,
333+
RawCompletionToken&& token, Args&&... args)
334+
{
335+
return async_initiate<Signatures...>(
336+
std::forward<Initiation>(initiation),
337+
::ceph::async::redirect_error_t<
338+
default_completion_token_t<associated_executor_t<Initiation>>,
339+
Disposition>(
340+
default_completion_token_t<associated_executor_t<Initiation>>{},
341+
token.disposition), std::forward<Args>(args)...);
342+
}
343+
};
344+
} // namespace boost::asio

src/test/common/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,3 +506,6 @@ add_executable(unittest_async_cond test_async_cond.cc)
506506
target_link_libraries(unittest_async_cond GTest::GTest)
507507
add_ceph_unittest(unittest_async_cond)
508508

509+
add_executable(unittest_async_redirect_error test_async_redirect_error.cc)
510+
target_link_libraries(unittest_async_redirect_error GTest::GTest)
511+
add_ceph_unittest(unittest_async_redirect_error)

0 commit comments

Comments
 (0)