]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | |
2 | // Copyright Oliver Kowalke 2013. | |
3 | // Distributed under the Boost Software License, Version 1.0. | |
4 | // (See accompanying file LICENSE_1_0.txt or copy at | |
5 | // http://www.boost.org/LICENSE_1_0.txt) | |
6 | ||
7 | #ifndef BOOST_FIBERS_CONTEXT_H | |
8 | #define BOOST_FIBERS_CONTEXT_H | |
9 | ||
10 | #include <atomic> | |
11 | #include <chrono> | |
12 | #include <exception> | |
13 | #include <functional> | |
14 | #include <map> | |
15 | #include <memory> | |
16 | #include <type_traits> | |
17 | ||
18 | #include <boost/assert.hpp> | |
19 | #include <boost/config.hpp> | |
20 | #include <boost/context/detail/apply.hpp> | |
21 | #include <boost/context/execution_context.hpp> | |
22 | #include <boost/context/stack_context.hpp> | |
23 | #include <boost/intrusive/list.hpp> | |
24 | #include <boost/intrusive/parent_from_member.hpp> | |
25 | #include <boost/intrusive_ptr.hpp> | |
26 | #include <boost/intrusive/set.hpp> | |
27 | ||
28 | #include <boost/fiber/detail/config.hpp> | |
29 | #include <boost/fiber/detail/data.hpp> | |
30 | #include <boost/fiber/detail/decay_copy.hpp> | |
31 | #include <boost/fiber/detail/fss.hpp> | |
32 | #include <boost/fiber/detail/spinlock.hpp> | |
33 | #include <boost/fiber/detail/wrap.hpp> | |
34 | #include <boost/fiber/exceptions.hpp> | |
35 | #include <boost/fiber/fixedsize_stack.hpp> | |
36 | #include <boost/fiber/policy.hpp> | |
37 | #include <boost/fiber/properties.hpp> | |
38 | #include <boost/fiber/segmented_stack.hpp> | |
39 | #include <boost/fiber/type.hpp> | |
40 | ||
41 | #ifdef BOOST_HAS_ABI_HEADERS | |
42 | # include BOOST_ABI_PREFIX | |
43 | #endif | |
44 | ||
45 | #ifdef _MSC_VER | |
46 | # pragma warning(push) | |
47 | # pragma warning(disable:4251) | |
48 | #endif | |
49 | ||
50 | namespace boost { | |
51 | namespace fibers { | |
52 | ||
53 | class context; | |
54 | class fiber; | |
55 | class scheduler; | |
56 | ||
57 | namespace detail { | |
58 | ||
59 | struct wait_tag; | |
60 | typedef intrusive::list_member_hook< | |
61 | intrusive::tag< wait_tag >, | |
62 | intrusive::link_mode< | |
63 | intrusive::auto_unlink | |
64 | > | |
65 | > wait_hook; | |
66 | // declaration of the functor that converts between | |
67 | // the context class and the wait-hook | |
68 | struct wait_functor { | |
69 | // required types | |
70 | typedef wait_hook hook_type; | |
71 | typedef hook_type * hook_ptr; | |
72 | typedef const hook_type * const_hook_ptr; | |
73 | typedef context value_type; | |
74 | typedef value_type * pointer; | |
75 | typedef const value_type * const_pointer; | |
76 | ||
77 | // required static functions | |
78 | static hook_ptr to_hook_ptr( value_type &value); | |
79 | static const_hook_ptr to_hook_ptr( value_type const& value); | |
80 | static pointer to_value_ptr( hook_ptr n); | |
81 | static const_pointer to_value_ptr( const_hook_ptr n); | |
82 | }; | |
83 | ||
84 | struct ready_tag; | |
85 | typedef intrusive::list_member_hook< | |
86 | intrusive::tag< ready_tag >, | |
87 | intrusive::link_mode< | |
88 | intrusive::auto_unlink | |
89 | > | |
90 | > ready_hook; | |
91 | ||
92 | struct sleep_tag; | |
93 | typedef intrusive::set_member_hook< | |
94 | intrusive::tag< sleep_tag >, | |
95 | intrusive::link_mode< | |
96 | intrusive::auto_unlink | |
97 | > | |
98 | > sleep_hook; | |
99 | ||
100 | struct terminated_tag; | |
101 | typedef intrusive::list_member_hook< | |
102 | intrusive::tag< terminated_tag >, | |
103 | intrusive::link_mode< | |
104 | intrusive::auto_unlink | |
105 | > | |
106 | > terminated_hook; | |
107 | ||
108 | struct worker_tag; | |
109 | typedef intrusive::list_member_hook< | |
110 | intrusive::tag< worker_tag >, | |
111 | intrusive::link_mode< | |
112 | intrusive::auto_unlink | |
113 | > | |
114 | > worker_hook; | |
115 | ||
116 | } | |
117 | ||
118 | struct main_context_t {}; | |
119 | const main_context_t main_context{}; | |
120 | ||
121 | struct dispatcher_context_t {}; | |
122 | const dispatcher_context_t dispatcher_context{}; | |
123 | ||
124 | struct worker_context_t {}; | |
125 | const worker_context_t worker_context{}; | |
126 | ||
127 | class BOOST_FIBERS_DECL context { | |
128 | private: | |
129 | friend class scheduler; | |
130 | ||
131 | enum flag_t { | |
132 | flag_terminated = 1 << 1 | |
133 | }; | |
134 | ||
135 | struct fss_data { | |
136 | void * vp{ nullptr }; | |
137 | detail::fss_cleanup_function::ptr_t cleanup_function{}; | |
138 | ||
139 | fss_data() noexcept { | |
140 | } | |
141 | ||
142 | fss_data( void * vp_, | |
143 | detail::fss_cleanup_function::ptr_t const& fn) noexcept : | |
144 | vp( vp_), | |
145 | cleanup_function( fn) { | |
146 | BOOST_ASSERT( cleanup_function); | |
147 | } | |
148 | ||
149 | void do_cleanup() { | |
150 | ( * cleanup_function)( vp); | |
151 | } | |
152 | }; | |
153 | ||
154 | typedef std::map< uintptr_t, fss_data > fss_data_t; | |
155 | ||
156 | #if ! defined(BOOST_FIBERS_NO_ATOMICS) | |
157 | std::atomic< std::size_t > use_count_{ 0 }; | |
158 | std::atomic< unsigned int > flags_; | |
159 | std::atomic< type > type_; | |
160 | #else | |
161 | std::size_t use_count_{ 0 }; | |
162 | unsigned int flags_; | |
163 | type type_; | |
164 | #endif | |
165 | launch policy_{ launch::post }; | |
166 | scheduler * scheduler_{ nullptr }; | |
167 | #if (BOOST_EXECUTION_CONTEXT==1) | |
168 | boost::context::execution_context ctx_; | |
169 | #else | |
170 | boost::context::execution_context< detail::data_t * > ctx_; | |
171 | #endif | |
172 | ||
173 | void resume_( detail::data_t &) noexcept; | |
174 | void set_ready_( context *) noexcept; | |
175 | ||
176 | #if (BOOST_EXECUTION_CONTEXT==1) | |
177 | template< typename Fn, typename Tpl > | |
178 | void run_( Fn && fn_, Tpl && tpl_, detail::data_t * dp) noexcept { | |
179 | { | |
180 | // fn and tpl must be destroyed before calling set_terminated() | |
181 | typename std::decay< Fn >::type fn = std::forward< Fn >( fn_); | |
182 | typename std::decay< Tpl >::type tpl = std::forward< Tpl >( tpl_); | |
183 | if ( nullptr != dp->lk) { | |
184 | dp->lk->unlock(); | |
185 | } else if ( nullptr != dp->ctx) { | |
186 | active()->set_ready_( dp->ctx); | |
187 | } | |
188 | boost::context::detail::apply( std::move( fn), std::move( tpl) ); | |
189 | } | |
190 | // terminate context | |
191 | set_terminated(); | |
192 | BOOST_ASSERT_MSG( false, "fiber already terminated"); | |
193 | } | |
194 | #else | |
195 | template< typename Fn, typename Tpl > | |
196 | boost::context::execution_context< detail::data_t * > | |
197 | run_( boost::context::execution_context< detail::data_t * > ctx, Fn && fn_, Tpl && tpl_, detail::data_t * dp) noexcept { | |
198 | { | |
199 | // fn and tpl must be destroyed before calling set_terminated() | |
200 | typename std::decay< Fn >::type fn = std::forward< Fn >( fn_); | |
201 | typename std::decay< Tpl >::type tpl = std::forward< Tpl >( tpl_); | |
202 | // update execution_context of calling fiber | |
203 | dp->from->ctx_ = std::move( ctx); | |
204 | if ( nullptr != dp->lk) { | |
205 | dp->lk->unlock(); | |
206 | } else if ( nullptr != dp->ctx) { | |
207 | active()->set_ready_( dp->ctx); | |
208 | } | |
209 | boost::context::detail::apply( std::move( fn), std::move( tpl) ); | |
210 | } | |
211 | // terminate context | |
212 | return set_terminated(); | |
213 | } | |
214 | #endif | |
215 | ||
216 | public: | |
217 | detail::ready_hook ready_hook_{}; | |
218 | detail::sleep_hook sleep_hook_{}; | |
219 | detail::terminated_hook terminated_hook_{}; | |
220 | detail::wait_hook wait_hook_{}; | |
221 | detail::worker_hook worker_hook_{}; | |
222 | std::chrono::steady_clock::time_point tp_{ (std::chrono::steady_clock::time_point::max)() }; | |
223 | ||
224 | typedef intrusive::list< | |
225 | context, | |
226 | intrusive::function_hook< detail::wait_functor >, | |
227 | intrusive::constant_time_size< false > > wait_queue_t; | |
228 | ||
229 | private: | |
230 | fss_data_t fss_data_{}; | |
231 | wait_queue_t wait_queue_{}; | |
232 | detail::spinlock splk_{}; | |
233 | fiber_properties * properties_{ nullptr }; | |
234 | ||
235 | public: | |
236 | class id { | |
237 | private: | |
238 | context * impl_{ nullptr }; | |
239 | ||
240 | public: | |
241 | id() noexcept { | |
242 | } | |
243 | ||
244 | explicit id( context * impl) noexcept : | |
245 | impl_( impl) { | |
246 | } | |
247 | ||
248 | bool operator==( id const& other) const noexcept { | |
249 | return impl_ == other.impl_; | |
250 | } | |
251 | ||
252 | bool operator!=( id const& other) const noexcept { | |
253 | return impl_ != other.impl_; | |
254 | } | |
255 | ||
256 | bool operator<( id const& other) const noexcept { | |
257 | return impl_ < other.impl_; | |
258 | } | |
259 | ||
260 | bool operator>( id const& other) const noexcept { | |
261 | return other.impl_ < impl_; | |
262 | } | |
263 | ||
264 | bool operator<=( id const& other) const noexcept { | |
265 | return ! ( * this > other); | |
266 | } | |
267 | ||
268 | bool operator>=( id const& other) const noexcept { | |
269 | return ! ( * this < other); | |
270 | } | |
271 | ||
272 | template< typename charT, class traitsT > | |
273 | friend std::basic_ostream< charT, traitsT > & | |
274 | operator<<( std::basic_ostream< charT, traitsT > & os, id const& other) { | |
275 | if ( nullptr != other.impl_) { | |
276 | return os << other.impl_; | |
277 | } else { | |
278 | return os << "{not-valid}"; | |
279 | } | |
280 | } | |
281 | ||
282 | explicit operator bool() const noexcept { | |
283 | return nullptr != impl_; | |
284 | } | |
285 | ||
286 | bool operator!() const noexcept { | |
287 | return nullptr == impl_; | |
288 | } | |
289 | }; | |
290 | ||
291 | static context * active() noexcept; | |
292 | ||
293 | static void reset_active() noexcept; | |
294 | ||
295 | // main fiber context | |
296 | explicit context( main_context_t) noexcept; | |
297 | ||
298 | // dispatcher fiber context | |
299 | context( dispatcher_context_t, boost::context::preallocated const&, | |
300 | default_stack const&, scheduler *); | |
301 | ||
302 | // worker fiber context | |
303 | template< typename StackAlloc, | |
304 | typename Fn, | |
305 | typename Tpl | |
306 | > | |
307 | context( worker_context_t, | |
308 | launch policy, | |
309 | boost::context::preallocated palloc, StackAlloc salloc, | |
310 | Fn && fn, Tpl && tpl) : | |
311 | use_count_{ 1 }, // fiber instance or scheduler owner | |
312 | flags_{ 0 }, | |
313 | type_{ type::worker_context }, | |
314 | policy_{ policy }, | |
315 | #if (BOOST_EXECUTION_CONTEXT==1) | |
316 | # if defined(BOOST_NO_CXX14_GENERIC_LAMBDAS) | |
317 | ctx_{ std::allocator_arg, palloc, salloc, | |
318 | detail::wrap( | |
319 | [this]( typename std::decay< Fn >::type & fn, typename std::decay< Tpl >::type & tpl, | |
320 | boost::context::execution_context & ctx, void * vp) mutable noexcept { | |
321 | run_( std::move( fn), std::move( tpl), static_cast< detail::data_t * >( vp) ); | |
322 | }, | |
323 | std::forward< Fn >( fn), | |
324 | std::forward< Tpl >( tpl), | |
325 | boost::context::execution_context::current() ) | |
326 | } | |
327 | # else | |
328 | ctx_{ std::allocator_arg, palloc, salloc, | |
329 | [this,fn=detail::decay_copy( std::forward< Fn >( fn) ),tpl=std::forward< Tpl >( tpl), | |
330 | ctx=boost::context::execution_context::current()] (void * vp) mutable noexcept { | |
331 | run_( std::move( fn), std::move( tpl), static_cast< detail::data_t * >( vp) ); | |
332 | }} | |
333 | # endif | |
334 | #else | |
335 | # if defined(BOOST_NO_CXX14_GENERIC_LAMBDAS) | |
336 | ctx_{ std::allocator_arg, palloc, salloc, | |
337 | detail::wrap( | |
338 | [this]( typename std::decay< Fn >::type & fn, typename std::decay< Tpl >::type & tpl, | |
339 | boost::context::execution_context< detail::data_t * > ctx, detail::data_t * dp) mutable noexcept { | |
340 | return run_( std::move( ctx), std::move( fn), std::move( tpl), dp); | |
341 | }, | |
342 | std::forward< Fn >( fn), | |
343 | std::forward< Tpl >( tpl) )} | |
344 | ||
345 | # else | |
346 | ctx_{ std::allocator_arg, palloc, salloc, | |
347 | [this,fn=detail::decay_copy( std::forward< Fn >( fn) ),tpl=std::forward< Tpl >( tpl)] | |
348 | (boost::context::execution_context< detail::data_t * > ctx, detail::data_t * dp) mutable noexcept { | |
349 | return run_( std::move( ctx), std::move( fn), std::move( tpl), dp); | |
350 | }} | |
351 | # endif | |
352 | #endif | |
353 | {} | |
354 | ||
355 | context( context const&) = delete; | |
356 | context & operator=( context const&) = delete; | |
357 | ||
358 | virtual ~context(); | |
359 | ||
360 | scheduler * get_scheduler() const noexcept; | |
361 | ||
362 | id get_id() const noexcept; | |
363 | ||
364 | void resume() noexcept; | |
365 | void resume( detail::spinlock_lock &) noexcept; | |
366 | void resume( context *) noexcept; | |
367 | ||
368 | void suspend() noexcept; | |
369 | void suspend( detail::spinlock_lock &) noexcept; | |
370 | ||
371 | #if (BOOST_EXECUTION_CONTEXT==1) | |
372 | void set_terminated() noexcept; | |
373 | #else | |
374 | boost::context::execution_context< detail::data_t * > suspend_with_cc() noexcept; | |
375 | boost::context::execution_context< detail::data_t * > set_terminated() noexcept; | |
376 | #endif | |
377 | void join(); | |
378 | ||
379 | void yield() noexcept; | |
380 | ||
381 | bool wait_until( std::chrono::steady_clock::time_point const&) noexcept; | |
382 | bool wait_until( std::chrono::steady_clock::time_point const&, | |
383 | detail::spinlock_lock &) noexcept; | |
384 | ||
385 | void set_ready( context *) noexcept; | |
386 | ||
387 | bool is_context( type t) const noexcept { | |
388 | return type::none != ( type_ & t); | |
389 | } | |
390 | ||
391 | bool is_terminated() const noexcept { | |
392 | return 0 != ( flags_ & flag_terminated); | |
393 | } | |
394 | ||
395 | void * get_fss_data( void const * vp) const; | |
396 | ||
397 | void set_fss_data( | |
398 | void const * vp, | |
399 | detail::fss_cleanup_function::ptr_t const& cleanup_fn, | |
400 | void * data, | |
401 | bool cleanup_existing); | |
402 | ||
403 | void set_properties( fiber_properties * props) noexcept; | |
404 | ||
405 | fiber_properties * get_properties() const noexcept { | |
406 | return properties_; | |
407 | } | |
408 | ||
409 | launch get_policy() const noexcept { | |
410 | return policy_; | |
411 | } | |
412 | ||
413 | bool ready_is_linked() const noexcept; | |
414 | ||
415 | bool sleep_is_linked() const noexcept; | |
416 | ||
417 | bool terminated_is_linked() const noexcept; | |
418 | ||
419 | bool wait_is_linked() const noexcept; | |
420 | ||
421 | bool worker_is_linked() const noexcept; | |
422 | ||
423 | template< typename List > | |
424 | void ready_link( List & lst) noexcept { | |
425 | static_assert( std::is_same< typename List::value_traits::hook_type, detail::ready_hook >::value, "not a ready-queue"); | |
426 | lst.push_back( * this); | |
427 | } | |
428 | ||
429 | template< typename Set > | |
430 | void sleep_link( Set & set) noexcept { | |
431 | static_assert( std::is_same< typename Set::value_traits::hook_type,detail::sleep_hook >::value, "not a sleep-queue"); | |
432 | set.insert( * this); | |
433 | } | |
434 | ||
435 | template< typename List > | |
436 | void terminated_link( List & lst) noexcept { | |
437 | static_assert( std::is_same< typename List::value_traits::hook_type, detail::terminated_hook >::value, "not a terminated-queue"); | |
438 | lst.push_back( * this); | |
439 | } | |
440 | ||
441 | template< typename List > | |
442 | void wait_link( List & lst) noexcept { | |
443 | static_assert( std::is_same< typename List::value_traits::hook_type, detail::wait_hook >::value, "not a wait-queue"); | |
444 | lst.push_back( * this); | |
445 | } | |
446 | ||
447 | template< typename List > | |
448 | void worker_link( List & lst) noexcept { | |
449 | static_assert( std::is_same< typename List::value_traits::hook_type, detail::worker_hook >::value, "not a worker-queue"); | |
450 | lst.push_back( * this); | |
451 | } | |
452 | ||
453 | void ready_unlink() noexcept; | |
454 | ||
455 | void sleep_unlink() noexcept; | |
456 | ||
457 | void wait_unlink() noexcept; | |
458 | ||
459 | void worker_unlink() noexcept; | |
460 | ||
461 | void detach() noexcept; | |
462 | ||
463 | void attach( context *) noexcept; | |
464 | ||
465 | friend void intrusive_ptr_add_ref( context * ctx) noexcept { | |
466 | BOOST_ASSERT( nullptr != ctx); | |
467 | ++ctx->use_count_; | |
468 | } | |
469 | ||
470 | friend void intrusive_ptr_release( context * ctx) noexcept { | |
471 | BOOST_ASSERT( nullptr != ctx); | |
472 | if ( 0 == --ctx->use_count_) { | |
473 | #if (BOOST_EXECUTION_CONTEXT==1) | |
474 | boost::context::execution_context ec( ctx->ctx_); | |
475 | // destruct context | |
476 | // deallocates stack (execution_context is ref counted) | |
477 | ctx->~context(); | |
478 | #else | |
479 | boost::context::execution_context< detail::data_t * > cc( std::move( ctx->ctx_) ); | |
480 | // destruct context | |
481 | ctx->~context(); | |
482 | // deallocated stack | |
483 | cc( nullptr); | |
484 | #endif | |
485 | } | |
486 | } | |
487 | }; | |
488 | ||
489 | inline | |
490 | bool operator<( context const& l, context const& r) noexcept { | |
491 | return l.get_id() < r.get_id(); | |
492 | } | |
493 | ||
494 | template< typename StackAlloc, typename Fn, typename ... Args > | |
495 | static intrusive_ptr< context > make_worker_context( launch policy, | |
496 | StackAlloc salloc, | |
497 | Fn && fn, Args && ... args) { | |
498 | boost::context::stack_context sctx = salloc.allocate(); | |
499 | #if defined(BOOST_NO_CXX14_CONSTEXPR) || defined(BOOST_NO_CXX11_STD_ALIGN) | |
500 | // reserve space for control structure | |
501 | const std::size_t size = sctx.size - sizeof( context); | |
502 | void * sp = static_cast< char * >( sctx.sp) - sizeof( context); | |
503 | #else | |
504 | constexpr std::size_t func_alignment = 64; // alignof( context); | |
505 | constexpr std::size_t func_size = sizeof( context); | |
506 | // reserve space on stack | |
507 | void * sp = static_cast< char * >( sctx.sp) - func_size - func_alignment; | |
508 | // align sp pointer | |
509 | std::size_t space = func_size + func_alignment; | |
510 | sp = std::align( func_alignment, func_size, sp, space); | |
511 | BOOST_ASSERT( nullptr != sp); | |
512 | // calculate remaining size | |
513 | const std::size_t size = sctx.size - ( static_cast< char * >( sctx.sp) - static_cast< char * >( sp) ); | |
514 | #endif | |
515 | // placement new of context on top of fiber's stack | |
516 | return intrusive_ptr< context >( | |
517 | ::new ( sp) context( | |
518 | worker_context, | |
519 | policy, | |
520 | boost::context::preallocated( sp, size, sctx), | |
521 | salloc, | |
522 | std::forward< Fn >( fn), | |
523 | std::make_tuple( std::forward< Args >( args) ... ) ) ); | |
524 | } | |
525 | ||
526 | namespace detail { | |
527 | ||
528 | inline | |
529 | wait_functor::hook_ptr wait_functor::to_hook_ptr( wait_functor::value_type & value) { | |
530 | return & value.wait_hook_; | |
531 | } | |
532 | ||
533 | inline | |
534 | wait_functor::const_hook_ptr wait_functor::to_hook_ptr( wait_functor::value_type const& value) { | |
535 | return & value.wait_hook_; | |
536 | } | |
537 | ||
538 | inline | |
539 | wait_functor::pointer wait_functor::to_value_ptr( wait_functor::hook_ptr n) { | |
540 | return intrusive::get_parent_from_member< context >( n, & context::wait_hook_); | |
541 | } | |
542 | ||
543 | inline | |
544 | wait_functor::const_pointer wait_functor::to_value_ptr( wait_functor::const_hook_ptr n) { | |
545 | return intrusive::get_parent_from_member< context >( n, & context::wait_hook_); | |
546 | } | |
547 | ||
548 | }}} | |
549 | ||
550 | #ifdef _MSC_VER | |
551 | # pragma warning(pop) | |
552 | #endif | |
553 | ||
554 | #ifdef BOOST_HAS_ABI_HEADERS | |
555 | # include BOOST_ABI_SUFFIX | |
556 | #endif | |
557 | ||
558 | #endif // BOOST_FIBERS_CONTEXT_H |