source: mainline/uspace/lib/cpp/include/__bits/thread/shared_state.hpp@ 3a29607

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 3a29607 was 6e97265, checked in by Jaroslav Jindrak <dzejrou@…>, 6 years ago

cpp: refactor future to avoid code duplication, fix wait_for and wait_until, synchronization and compilation

  • Property mode set to 100644
File size: 10.3 KB
Line 
1/*
2 * Copyright (c) 2019 Jaroslav Jindrak
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#ifndef LIBCPP_BITS_THREAD_SHARED_STATE
30#define LIBCPP_BITS_THREAD_SHARED_STATE
31
32/**
33 * 30.6.4, shared state:
34 */
35
36#include <__bits/exception.hpp>
37#include <__bits/functional/function.hpp>
38#include <__bits/functional/invoke.hpp>
39#include <__bits/refcount_obj.hpp>
40#include <__bits/thread/future_common.hpp>
41#include <__bits/thread/threading.hpp>
42#include <cerrno>
43#include <thread>
44#include <tuple>
45
46namespace std
47{
48 enum class future_status;
49}
50
51namespace std::aux
52{
53 template<class R>
54 class shared_state: public aux::refcount_obj
55 {
56 public:
57 shared_state()
58 : mutex_{}, condvar_{}, value_{}, value_set_{false},
59 exception_{}, has_exception_{false}
60 {
61 threading::mutex::init(mutex_);
62 threading::condvar::init(condvar_);
63 }
64
65 void destroy() override
66 {
67 /**
68 * Note: No need to act in this case, async shared
69 * state is the object that needs to sometimes
70 * invoke its payload.
71 */
72 }
73
74 void set_value(const R& val, bool set)
75 {
76 /**
77 * Note: This is the 'mark ready' move described
78 * in 30.6.4 (6).
79 */
80
81 aux::threading::mutex::lock(mutex_);
82 value_ = val;
83 value_set_ = set;
84 aux::threading::mutex::unlock(mutex_);
85
86 aux::threading::condvar::broadcast(condvar_);
87 }
88
89 void set_value(R&& val, bool set = true)
90 {
91 aux::threading::mutex::lock(mutex_);
92 value_ = std::move(val);
93 value_set_ = set;
94 aux::threading::mutex::unlock(mutex_);
95
96 aux::threading::condvar::broadcast(condvar_);
97 }
98
99 void mark_set(bool set = true) noexcept
100 {
101 value_set_ = set;
102 }
103
104 bool is_set() const noexcept
105 {
106 return value_set_;
107 }
108
109 R& get()
110 {
111 return value_;
112 }
113
114 void set_exception(exception_ptr ptr)
115 {
116 exception_ = ptr;
117 has_exception_ = true;
118 }
119
120 bool has_exception() const noexcept
121 {
122 return has_exception_;
123 }
124
125 void throw_stored_exception() const
126 {
127 // TODO: implement
128 }
129
130 /**
131 * TODO: This member function is supposed to be marked
132 * as 'const'. In such a case, however, we cannot
133 * use the underlying fibril API because these
134 * references get converted to pointers and the API
135 * does not accept e.g. 'const fibril_condvar_t*'.
136 *
137 * The same applies to the wait_for and wait_until
138 * functions.
139 */
140 virtual void wait()
141 {
142 aux::threading::mutex::lock(mutex_);
143 while (!value_set_)
144 aux::threading::condvar::wait(condvar_, mutex_);
145 aux::threading::mutex::unlock(mutex_);
146 }
147
148 template<class Rep, class Period>
149 future_status
150 wait_for(const chrono::duration<Rep, Period>& rel_time)
151 {
152 aux::threading::mutex::lock(mutex_);
153 auto res = timed_wait_(
154 aux::threading::time::convert(rel_time)
155 );
156 aux::threading::mutex::unlock(mutex_);
157
158 return res;
159 }
160
161 template<class Clock, class Duration>
162 future_status
163 wait_until(const chrono::time_point<Clock, Duration>& abs_time)
164 {
165 aux::threading::mutex::lock(mutex_);
166 auto res = timed_wait_(
167 aux::threading::time(convert(abs_time - Clock::now()))
168 );
169 aux::threading::mutex::unlock(mutex_);
170
171 return res;
172 }
173
174 ~shared_state() override = default;
175
176 protected:
177 aux::mutex_t mutex_;
178 aux::condvar_t condvar_;
179
180 R value_;
181 bool value_set_;
182
183 exception_ptr exception_;
184 bool has_exception_;
185
186 /**
187 * Note: wait_for and wait_until are templates and as such
188 * cannot be virtual and overriden by the deferred_ and
189 * async_ children. However, we are using aux::time_unit_t
190 * in the end anyway, so we can work around that
191 * by using the 'template method' design pattern
192 * (i.e. by providing a virtual function called by these
193 * templates and then overriding that function in the
194 * children).
195 */
196 virtual future_status timed_wait_(aux::time_unit_t time)
197 {
198 auto res = aux::threading::condvar::wait_for(
199 condvar_, mutex_, time
200 );
201
202 return res == ETIMEOUT ? future_status::timeout
203 : future_status::ready;
204 }
205 };
206
207 /**
208 * We could make one state for both async and
209 * deferred policies, but then we would be wasting
210 * memory and the only benefit would be the ability
211 * for additional implementation defined policies done
212 * directly in that state (as opposed to making new
213 * states for them).
214 *
215 * But since we have no plan (nor need) to make those,
216 * this approach seems to be the best one.
217 */
218
219 template<class R, class F, class... Args>
220 class async_shared_state: public shared_state<R>
221 {
222 public:
223 async_shared_state(F&& f, Args&&... args)
224 : shared_state<R>{}, thread_{}
225 {
226 thread_ = thread{
227 [=](){
228 try
229 {
230 this->set_value(invoke(f, args...));
231 }
232 catch(...) // TODO: Any exception.
233 {
234 // TODO: Store it.
235 }
236 }
237 };
238 }
239
240 void destroy() override
241 {
242 if (!this->is_set())
243 thread_.join();
244 }
245
246 void wait() override
247 {
248 if (!this->is_set())
249 thread_.join();
250 }
251
252 ~async_shared_state() override
253 {
254 destroy();
255 }
256
257 protected:
258 future_status timed_wait_(aux::time_unit_t) override
259 {
260 // TODO: have to sleep and check
261 return future_status::timeout;
262 }
263
264 private:
265 thread thread_;
266 };
267
268 template<class R, class F, class... Args>
269 class deferred_shared_state: public shared_state<R>
270 {
271 public:
272 template<class G>
273 deferred_shared_state(G&& f, Args&&... args)
274 : shared_state<R>{}, func_{forward<F>(f)},
275 args_{forward<Args>(args)...}
276 { /* DUMMY BODY */ }
277
278 void destroy() override
279 {
280 aux::threading::mutex::lock(this->mutex_);
281 if (!this->is_set())
282 invoke_(make_index_sequence<sizeof...(Args)>{});
283 aux::threading::mutex::unlock(this->mutex_);
284 }
285
286 void wait() override
287 {
288 /**
289 * Note: Synchronization done in invoke_ -> set_value.
290 */
291 if (!this->is_set())
292 invoke_(make_index_sequence<sizeof...(Args)>{});
293 }
294
295 ~deferred_shared_state() override
296 {
297 destroy();
298 }
299
300 protected:
301 function<R(decay_t<Args>...)> func_;
302 tuple<decay_t<Args>...> args_;
303
304 template<size_t... Is>
305 void invoke_(index_sequence<Is...>)
306 {
307 try
308 {
309 this->set_value(invoke(move(func_), get<Is>(move(args_))...));
310 }
311 catch(...)
312 {
313 // TODO: Store it.
314 }
315 }
316
317 future_status timed_wait_(aux::time_unit_t) override
318 {
319 /**
320 * Note: Neither of the wait_ functions has any effect
321 * for deferred functions spawned by async (which
322 * are the only users of this state type).
323 */
324 return future_status::deferred;
325 }
326 };
327}
328
329#endif
Note: See TracBrowser for help on using the repository browser.