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

Last change on this file since e49d0ac was b57ba05, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 3 years ago

Update headers in C++ files

  • Property mode set to 100644
File size: 12.0 KB
Line 
1/*
2 * SPDX-FileCopyrightText: 2019 Jaroslav Jindrak
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7#ifndef LIBCPP_BITS_THREAD_SHARED_STATE
8#define LIBCPP_BITS_THREAD_SHARED_STATE
9
10/**
11 * 30.6.4, shared state:
12 */
13
14#include <__bits/exception.hpp>
15#include <__bits/functional/function.hpp>
16#include <__bits/functional/invoke.hpp>
17#include <__bits/refcount_obj.hpp>
18#include <__bits/thread/future_common.hpp>
19#include <__bits/thread/threading.hpp>
20#include <cerrno>
21#include <thread>
22#include <tuple>
23
24namespace std::aux
25{
26 class shared_state_base: public aux::refcount_obj
27 {
28 public:
29 shared_state_base()
30 : mutex_{}, condvar_{}, value_set_{false},
31 exception_{}, has_exception_{false}
32 {
33 threading::mutex::init(mutex_);
34 threading::condvar::init(condvar_);
35 }
36
37 void destroy() override
38 {
39 /**
40 * Note: No need to act in this case, async shared
41 * state is the object that needs to sometimes
42 * invoke its payload.
43 */
44 }
45
46 void mark_set(bool set = true) noexcept
47 {
48 value_set_ = set;
49 }
50
51 bool is_set() const noexcept
52 {
53 return value_set_;
54 }
55
56 void set_exception(exception_ptr ptr, bool set = true)
57 {
58 exception_ = ptr;
59 has_exception_ = set;
60 }
61
62 bool has_exception() const noexcept
63 {
64 return has_exception_;
65 }
66
67 void throw_stored_exception() const
68 {
69 if (has_exception_)
70 rethrow_exception(exception_);
71 }
72
73 virtual void wait() const
74 {
75 aux::threading::mutex::lock(
76 const_cast<aux::mutex_t&>(mutex_)
77 );
78
79 while (!value_set_)
80 {
81 aux::threading::condvar::wait(
82 const_cast<aux::condvar_t&>(condvar_),
83 const_cast<aux::mutex_t&>(mutex_)
84 );
85 }
86
87 aux::threading::mutex::unlock(
88 const_cast<aux::mutex_t&>(mutex_)
89 );
90 }
91
92 template<class Rep, class Period>
93 future_status
94 wait_for(const chrono::duration<Rep, Period>& rel_time) const
95 {
96 if (value_set_)
97 return future_status::ready;
98
99 aux::threading::mutex::lock(
100 const_cast<aux::mutex_t&>(mutex_)
101 );
102
103 auto res = timed_wait_(
104 aux::threading::time::convert(rel_time)
105 );
106
107 aux::threading::mutex::unlock(
108 const_cast<aux::mutex_t&>(mutex_)
109 );
110
111 return res;
112 }
113
114 template<class Clock, class Duration>
115 future_status
116 wait_until(const chrono::time_point<Clock, Duration>& abs_time)
117 {
118 if (value_set_)
119 return future_status::ready;
120
121 aux::threading::mutex::lock(
122 const_cast<aux::mutex_t&>(mutex_)
123 );
124
125 auto res = timed_wait_(
126 aux::threading::time::convert(abs_time - Clock::now())
127 );
128
129 aux::threading::mutex::unlock(
130 const_cast<aux::mutex_t&>(mutex_)
131 );
132
133 return res;
134 }
135
136 ~shared_state_base() override = default;
137
138 protected:
139 aux::mutex_t mutex_;
140 aux::condvar_t condvar_;
141
142 bool value_set_;
143
144 exception_ptr exception_;
145 bool has_exception_;
146
147 /**
148 * Note: wait_for and wait_until are templates and as such
149 * cannot be virtual and overriden by the deferred_ and
150 * async_ children. However, we are using aux::time_unit_t
151 * in the end anyway, so we can work around that
152 * by using the 'template method' design pattern
153 * (i.e. by providing a virtual function called by these
154 * templates and then overriding that function in the
155 * children).
156 */
157 virtual future_status timed_wait_(aux::time_unit_t time) const
158 {
159 auto res = aux::threading::condvar::wait_for(
160 const_cast<aux::condvar_t&>(condvar_),
161 const_cast<aux::mutex_t&>(mutex_), time
162 );
163
164 return res == ETIMEOUT ? future_status::timeout
165 : future_status::ready;
166 }
167 };
168
169 template<class R>
170 class shared_state: public shared_state_base
171 {
172 public:
173 shared_state()
174 : shared_state_base{}
175 { /* DUMMY BODY */ }
176
177 void set_value(const R& val, bool set)
178 {
179 /**
180 * Note: This is the 'mark ready' move described
181 * in 30.6.4 (6).
182 */
183
184 aux::threading::mutex::lock(mutex_);
185 value_ = val;
186 value_set_ = set;
187 aux::threading::mutex::unlock(mutex_);
188
189 if (set)
190 aux::threading::condvar::broadcast(condvar_);
191 }
192
193 void set_value(R&& val, bool set = true)
194 {
195 aux::threading::mutex::lock(mutex_);
196 value_ = std::move(val);
197 value_set_ = set;
198 aux::threading::mutex::unlock(mutex_);
199
200 if (set)
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() const override
284 {
285 if (!this->is_set())
286 const_cast<thread&>(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 time) const override
296 {
297 /**
298 * Note: Currently we have no timed join, but this
299 * behaviour should be compliant.
300 */
301 aux::threading::time::sleep(time);
302 if (this->value_set_)
303 return future_status::ready;
304 else
305 return future_status::timeout;
306 }
307
308 private:
309 thread thread_;
310 };
311
312 template<class R, class F, class... Args>
313 class deferred_shared_state: public shared_state<R>
314 {
315 public:
316 template<class G>
317 deferred_shared_state(G&& f, Args&&... args)
318 : shared_state<R>{}, func_{forward<F>(f)},
319 args_{forward<Args>(args)...}
320 { /* DUMMY BODY */ }
321
322 void destroy() override
323 {
324 aux::threading::mutex::lock(this->mutex_);
325 if (!this->is_set())
326 invoke_(make_index_sequence<sizeof...(Args)>{});
327 aux::threading::mutex::unlock(this->mutex_);
328 }
329
330 void wait() const override
331 {
332 /**
333 * Note: Synchronization done in invoke_ -> set_value.
334 */
335 if (!this->is_set())
336 {
337 const_cast<
338 deferred_shared_state<R, F, Args...>*
339 >(this)->invoke_(make_index_sequence<sizeof...(Args)>{});
340 }
341 }
342
343 ~deferred_shared_state() override
344 {
345 destroy();
346 }
347
348 protected:
349 function<R(decay_t<Args>...)> func_;
350 tuple<decay_t<Args>...> args_;
351
352 template<size_t... Is>
353 void invoke_(index_sequence<Is...>)
354 {
355 try
356 {
357 if constexpr (!is_same_v<R, void>)
358 this->set_value(invoke(move(func_), get<Is>(move(args_))...));
359 else
360 {
361 invoke(move(func_), get<Is>(move(args_))...);
362 this->mark_set(true);
363 }
364 }
365 catch(const exception& __exception)
366 {
367 this->set_exception(make_exception_ptr(__exception));
368 }
369 }
370
371 future_status timed_wait_(aux::time_unit_t) const override
372 {
373 /**
374 * Note: Neither of the wait_ functions has any effect
375 * for deferred functions spawned by async (which
376 * are the only users of this state type).
377 */
378 return future_status::deferred;
379 }
380 };
381
382 /**
383 * Note: The following two functions should:
384 * 1) Increment refcount.
385 * 2) Store ptr to a vector of shared_state_base ptrs
386 * (as those have ::mark_set member functions).
387 * 3) If not done already, register a function
388 * executing all these in the thread_atexit function
389 * once that is implemented.
390 */
391
392 template<class R>
393 void set_state_value_at_thread_exit(shared_state<R>* state)
394 {
395 // TODO: implement
396 __unimplemented();
397 }
398
399 template<class R>
400 void set_state_exception_at_thread_exit(shared_state<R>* state)
401 {
402 // TODO: implement
403 __unimplemented();
404 }
405}
406
407#endif
Note: See TracBrowser for help on using the repository browser.