source: mainline/uspace/lib/cpp/include/__bits/thread/shared_state.hpp@ 72786f38

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

cpp: moved all the at_exit logic to two functions which are to be implemented later to avoid TODO duplication throughout the code

  • Property mode set to 100644
File size: 12.5 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::aux
47{
48 class shared_state_base: public aux::refcount_obj
49 {
50 public:
51 shared_state_base()
52 : mutex_{}, condvar_{}, value_set_{false},
53 exception_{}, has_exception_{false}
54 {
55 threading::mutex::init(mutex_);
56 threading::condvar::init(condvar_);
57 }
58
59 void destroy() override
60 {
61 /**
62 * Note: No need to act in this case, async shared
63 * state is the object that needs to sometimes
64 * invoke its payload.
65 */
66 }
67
68 void mark_set(bool set = true) noexcept
69 {
70 value_set_ = set;
71 }
72
73 bool is_set() const noexcept
74 {
75 return value_set_;
76 }
77
78 void set_exception(exception_ptr ptr, bool set = true)
79 {
80 exception_ = ptr;
81 has_exception_ = set;
82 }
83
84 bool has_exception() const noexcept
85 {
86 return has_exception_;
87 }
88
89 void throw_stored_exception() const
90 {
91 if (has_exception_)
92 rethrow_exception(exception_);
93 }
94
95 /**
96 * TODO: This member function is supposed to be marked
97 * as 'const'. In such a case, however, we cannot
98 * use the underlying fibril API because these
99 * references get converted to pointers and the API
100 * does not accept e.g. 'const fibril_condvar_t*'.
101 *
102 * The same applies to the wait_for and wait_until
103 * functions.
104 */
105 virtual void wait()
106 {
107 aux::threading::mutex::lock(mutex_);
108 while (!value_set_)
109 aux::threading::condvar::wait(condvar_, mutex_);
110 aux::threading::mutex::unlock(mutex_);
111 }
112
113 template<class Rep, class Period>
114 future_status
115 wait_for(const chrono::duration<Rep, Period>& rel_time)
116 {
117 aux::threading::mutex::lock(mutex_);
118 auto res = timed_wait_(
119 aux::threading::time::convert(rel_time)
120 );
121 aux::threading::mutex::unlock(mutex_);
122
123 return res;
124 }
125
126 template<class Clock, class Duration>
127 future_status
128 wait_until(const chrono::time_point<Clock, Duration>& abs_time)
129 {
130 aux::threading::mutex::lock(mutex_);
131 auto res = timed_wait_(
132 aux::threading::time::convert(abs_time - Clock::now())
133 );
134 aux::threading::mutex::unlock(mutex_);
135
136 return res;
137 }
138
139 ~shared_state_base() override = default;
140
141 protected:
142 aux::mutex_t mutex_;
143 aux::condvar_t condvar_;
144
145 bool value_set_;
146
147 exception_ptr exception_;
148 bool has_exception_;
149
150 /**
151 * Note: wait_for and wait_until are templates and as such
152 * cannot be virtual and overriden by the deferred_ and
153 * async_ children. However, we are using aux::time_unit_t
154 * in the end anyway, so we can work around that
155 * by using the 'template method' design pattern
156 * (i.e. by providing a virtual function called by these
157 * templates and then overriding that function in the
158 * children).
159 */
160 virtual future_status timed_wait_(aux::time_unit_t time)
161 {
162 auto res = aux::threading::condvar::wait_for(
163 condvar_, mutex_, time
164 );
165
166 return res == ETIMEOUT ? future_status::timeout
167 : future_status::ready;
168 }
169 };
170
171 template<class R>
172 class shared_state: public shared_state_base
173 {
174 public:
175 shared_state()
176 : shared_state_base{}
177 { /* DUMMY BODY */ }
178
179 void set_value(const R& val, bool set)
180 {
181 /**
182 * Note: This is the 'mark ready' move described
183 * in 30.6.4 (6).
184 */
185
186 aux::threading::mutex::lock(mutex_);
187 value_ = val;
188 value_set_ = set;
189 aux::threading::mutex::unlock(mutex_);
190
191 aux::threading::condvar::broadcast(condvar_);
192 }
193
194 void set_value(R&& val, bool set = true)
195 {
196 aux::threading::mutex::lock(mutex_);
197 value_ = std::move(val);
198 value_set_ = set;
199 aux::threading::mutex::unlock(mutex_);
200
201 aux::threading::condvar::broadcast(condvar_);
202 }
203
204 R& get()
205 {
206 return value_;
207 }
208
209 protected:
210 R value_;
211 };
212
213 template<>
214 class shared_state<void>: public shared_state_base
215 {
216 public:
217 shared_state()
218 : shared_state_base{}
219 { /* DUMMY BODY */ }
220
221 void set_value()
222 {
223 value_set_ = true;
224 aux::threading::condvar::broadcast(condvar_);
225 }
226
227 void get()
228 { /* DUMMY BODY */ }
229 };
230
231 /**
232 * We could make one state for both async and
233 * deferred policies, but then we would be wasting
234 * memory and the only benefit would be the ability
235 * for additional implementation defined policies done
236 * directly in that state (as opposed to making new
237 * states for them).
238 *
239 * But since we have no plan (nor need) to make those,
240 * this approach seems to be the best one.
241 *
242 * Also note that unlike the parent class shared_state,
243 * we do not need to specialize these for void. This is because
244 * the difference in class contents are handled by the parent
245 * specialization and setting the value can be handled easily
246 * by if constexpr and checking for the equivalence of the
247 * R template parameter and void.
248 */
249
250 template<class R, class F, class... Args>
251 class async_shared_state: public shared_state<R>
252 {
253 public:
254 async_shared_state(F&& f, Args&&... args)
255 : shared_state<R>{}, thread_{}
256 {
257 thread_ = thread{
258 [=](){
259 try
260 {
261 if constexpr (!is_same_v<R, void>)
262 this->set_value(invoke(f, args...));
263 else
264 {
265 invoke(f, args...);
266 this->mark_set(true);
267 }
268 }
269 catch(const exception& __exception)
270 {
271 this->set_exception(make_exception_ptr(__exception));
272 }
273 }
274 };
275 }
276
277 void destroy() override
278 {
279 if (!this->is_set())
280 thread_.join();
281 }
282
283 void wait() override
284 {
285 if (!this->is_set())
286 thread_.join();
287 }
288
289 ~async_shared_state() override
290 {
291 destroy();
292 }
293
294 protected:
295 future_status timed_wait_(aux::time_unit_t) override
296 {
297 /**
298 * Note: Currently we have no timed join, but this
299 * behaviour should be compliant.
300 */
301 return future_status::timeout;
302 }
303
304 private:
305 thread thread_;
306 };
307
308 template<class R, class F, class... Args>
309 class deferred_shared_state: public shared_state<R>
310 {
311 public:
312 template<class G>
313 deferred_shared_state(G&& f, Args&&... args)
314 : shared_state<R>{}, func_{forward<F>(f)},
315 args_{forward<Args>(args)...}
316 { /* DUMMY BODY */ }
317
318 void destroy() override
319 {
320 aux::threading::mutex::lock(this->mutex_);
321 if (!this->is_set())
322 invoke_(make_index_sequence<sizeof...(Args)>{});
323 aux::threading::mutex::unlock(this->mutex_);
324 }
325
326 void wait() override
327 {
328 /**
329 * Note: Synchronization done in invoke_ -> set_value.
330 */
331 if (!this->is_set())
332 invoke_(make_index_sequence<sizeof...(Args)>{});
333 }
334
335 ~deferred_shared_state() override
336 {
337 destroy();
338 }
339
340 protected:
341 function<R(decay_t<Args>...)> func_;
342 tuple<decay_t<Args>...> args_;
343
344 template<size_t... Is>
345 void invoke_(index_sequence<Is...>)
346 {
347 try
348 {
349 if constexpr (!is_same_v<R, void>)
350 this->set_value(invoke(move(func_), get<Is>(move(args_))...));
351 else
352 {
353 invoke(move(func_), get<Is>(move(args_))...);
354 this->mark_set(true);
355 }
356 }
357 catch(const exception& __exception)
358 {
359 this->set_exception(make_exception_ptr(__exception));
360 }
361 }
362
363 future_status timed_wait_(aux::time_unit_t) override
364 {
365 /**
366 * Note: Neither of the wait_ functions has any effect
367 * for deferred functions spawned by async (which
368 * are the only users of this state type).
369 */
370 return future_status::deferred;
371 }
372 };
373
374 /**
375 * Note: The following two functions should:
376 * 1) Increment refcount.
377 * 2) Store ptr to a vector of shared_state_base ptrs
378 * (as those have ::mark_set member functions).
379 * 3) If not done already, register a function
380 * executing all these in the thread_atexit function
381 * once that is implemented.
382 */
383
384 template<class R>
385 void set_state_value_at_thread_exit(shared_state<R>* state)
386 {
387 // TODO: implement
388 }
389
390 template<class R>
391 void set_state_exception_at_thread_exit(shared_state<R>* state)
392 {
393 // TODO: implement
394 }
395}
396
397#endif
Note: See TracBrowser for help on using the repository browser.