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

Last change on this file was 46c66f8, checked in by Jaroslav Jindrak <dzejrou@…>, 6 years ago

cpp: apply requested changes

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