-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFiberRetriever.hpp
More file actions
193 lines (147 loc) · 5.3 KB
/
FiberRetriever.hpp
File metadata and controls
193 lines (147 loc) · 5.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// Copyright 2019 Stephan Menzel. Distributed under the Boost
// Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#pragma once
#include "MRedisConfig.hpp"
#include "MRedisResult.hpp"
#include "MRedisTypes.hpp"
#include <boost/fiber/all.hpp>
#include "tools/Assert.hpp"
#include <string>
#include <chrono>
#include <atomic>
#include <optional>
#include <cstdint>
namespace moose {
namespace mredis {
/*! @brief behavioral helper to allow for easier use of a fiber future to wait for the result of an async op
*/
template <typename Retval>
class FiberRetriever {
public:
FiberRetriever() = delete;
FiberRetriever(const FiberRetriever &) = delete;
/*! @brief initialize a new getter for one-time usage
@param n_timeout wait for this long before exception is returned
*/
FiberRetriever(const unsigned int n_timeout = 3)
: m_timeout{ n_timeout }
, m_promise{ std::make_shared<boost::fibers::promise< std::optional<Retval> > >() }
, m_used{ false } {
}
/*! @brief call either that or get the future
@return value if set, otherwise none
@throw redis_error on timeout or underlying condition
*/
std::optional<Retval> wait_for_response() {
MOOSE_ASSERT_MSG((!m_used), "Double use of FiberRetriever object");
using namespace moose::tools;
boost::fibers::future< std::optional<Retval> > future_value = m_promise->get_future();
// Now I think this wait_for would imply a yield... Meaning that other fiber will take over while this one waits
if (future_value.wait_for(std::chrono::seconds(m_timeout)) == boost::fibers::future_status::timeout) {
m_used.store(true);
BOOST_THROW_EXCEPTION(redis_error() << error_message("Timeout getting redis value"));
}
m_used.store(true);
// Now we must have a value of correct type as our callback already checked for that.
// This may still throw however
return boost::get< std::optional<Retval> >(future_value.get());
}
/*! @brief use this as a callback in AsyncClient calls
#moep also this is unsafe. When handing this into the get call, it might throw, calling the d'tor
on the retriever object but leaving this handler intact. When it is later than called, it will
crash. Fix by making this shared_from_this and handing in the shared_ptr rather than the
callback only.
*/
#if BOOST_MSVC
Callback responder() const {
static_assert(sizeof(Retval) == -1, "Do not use general responder function. Specialize for Retval type")
};
#else
Callback responder() const = delete;
#endif
private:
const unsigned int m_timeout;
std::shared_ptr< boost::fibers::promise< std::optional<Retval> > > m_promise;
std::atomic<bool> m_used; //!< to prevent double usage of this object, set to true after use
};
template<>
inline Callback FiberRetriever<std::string>::responder() const {
return [promise{ m_promise }](const RedisMessage &n_message) {
using namespace moose::tools;
try {
// translate the error into an exception that will throw when the caller get()s the future
if (is_error(n_message)) {
redis_error rerr = boost::get<redis_error>(n_message);
throw rerr;
}
if (is_null(n_message)) {
promise->set_value(std::nullopt);
return;
}
if (!is_string(n_message)) {
BOOST_THROW_EXCEPTION(redis_error()
<< error_message("Unexpected return type, not a string")
<< error_argument(n_message.which()));
}
promise->set_value(boost::get<std::string>(n_message));
} catch (const redis_error &err) {
promise->set_exception(std::make_exception_ptr(err));
}
};
}
template<>
inline Callback FiberRetriever<boost::int64_t>::responder() const {
return [promise{ m_promise }] (const RedisMessage &n_message) {
using namespace moose::tools;
try {
// translate the error into an exception that will throw when the caller get()s the future
if (is_error(n_message)) {
redis_error rerr = boost::get<redis_error>(n_message);
throw rerr;
}
if (is_null(n_message)) {
promise->set_value(std::nullopt);
return;
}
if (!is_int(n_message)) {
BOOST_THROW_EXCEPTION(redis_error()
<< error_message("Unexpected return type, not an int")
<< error_argument(n_message.which()));
}
promise->set_value(boost::get<std::int64_t>(n_message));
} catch (const redis_error &err) {
promise->set_exception(std::make_exception_ptr(err));
}
};
}
template<>
inline Callback FiberRetriever< std::vector<RedisMessage> >::responder() const {
return [promise{ m_promise }](const RedisMessage &n_message) {
using namespace moose::tools;
try {
// translate the error into an exception that will throw when the caller get()s the future
if (is_error(n_message)) {
redis_error rerr = boost::get<redis_error>(n_message);
throw rerr;
}
if (is_null(n_message)) {
promise->set_value(std::nullopt);
return;
}
if (!is_array(n_message)) {
BOOST_THROW_EXCEPTION(redis_error()
<< error_message("Unexpected return type, not an array")
<< error_argument(n_message.which()));
}
promise->set_value(boost::get< std::vector<RedisMessage> >(n_message));
} catch (const redis_error &err) {
promise->set_exception(std::make_exception_ptr(err));
}
};
}
#if defined(BOOST_MSVC)
MREDIS_API void FiberRetrievergetRidOfLNK4221();
#endif
}
}