fiber_ucontext.hpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. // Copyright Oliver Kowalke 2017.
  2. // Distributed under the Boost Software License, Version 1.0.
  3. // (See accompanying file LICENSE_1_0.txt or copy at
  4. // http://www.boost.org/LICENSE_1_0.txt)
  5. #ifndef BOOST_CONTEXT_FIBER_H
  6. #define BOOST_CONTEXT_FIBER_H
  7. #include <boost/predef.h>
  8. #if BOOST_OS_MACOS
  9. #define _XOPEN_SOURCE 600
  10. #endif
  11. extern "C" {
  12. #include <ucontext.h>
  13. }
  14. #include <boost/context/detail/config.hpp>
  15. #include <algorithm>
  16. #include <cstddef>
  17. #include <cstdint>
  18. #include <cstdlib>
  19. #include <cstring>
  20. #include <functional>
  21. #include <memory>
  22. #include <ostream>
  23. #include <system_error>
  24. #include <tuple>
  25. #include <utility>
  26. #include <boost/assert.hpp>
  27. #include <boost/config.hpp>
  28. #include <boost/context/detail/disable_overload.hpp>
  29. #if defined(BOOST_NO_CXX14_STD_EXCHANGE)
  30. #include <boost/context/detail/exchange.hpp>
  31. #endif
  32. #include <boost/context/detail/externc.hpp>
  33. #if defined(BOOST_NO_CXX17_STD_INVOKE)
  34. #include <boost/context/detail/invoke.hpp>
  35. #endif
  36. #include <boost/context/fixedsize_stack.hpp>
  37. #include <boost/context/flags.hpp>
  38. #include <boost/context/preallocated.hpp>
  39. #if defined(BOOST_USE_SEGMENTED_STACKS)
  40. #include <boost/context/segmented_stack.hpp>
  41. #endif
  42. #include <boost/context/stack_context.hpp>
  43. #ifdef BOOST_HAS_ABI_HEADERS
  44. # include BOOST_ABI_PREFIX
  45. #endif
  46. #ifdef BOOST_USE_TSAN
  47. #include <sanitizer/tsan_interface.h>
  48. #endif
  49. namespace boost {
  50. namespace context {
  51. namespace detail {
  52. // tampoline function
  53. // entered if the execution context
  54. // is resumed for the first time
  55. template< typename Record >
  56. static void fiber_entry_func( void * data) noexcept {
  57. Record * record = static_cast< Record * >( data);
  58. BOOST_ASSERT( nullptr != record);
  59. // start execution of toplevel context-function
  60. record->run();
  61. }
  62. struct BOOST_CONTEXT_DECL fiber_activation_record {
  63. ucontext_t uctx{};
  64. stack_context sctx{};
  65. bool main_ctx{ true };
  66. fiber_activation_record * from{ nullptr };
  67. std::function< fiber_activation_record*(fiber_activation_record*&) > ontop{};
  68. bool terminated{ false };
  69. bool force_unwind{ false };
  70. #if defined(BOOST_USE_ASAN)
  71. void * fake_stack{ nullptr };
  72. void * stack_bottom{ nullptr };
  73. std::size_t stack_size{ 0 };
  74. #endif
  75. #if defined(BOOST_USE_TSAN)
  76. void * tsan_fiber{ nullptr };
  77. bool destroy_tsan_fiber{ true };
  78. #endif
  79. static fiber_activation_record *& current() noexcept;
  80. // used for toplevel-context
  81. // (e.g. main context, thread-entry context)
  82. fiber_activation_record() {
  83. if ( BOOST_UNLIKELY( 0 != ::getcontext( & uctx) ) ) {
  84. throw std::system_error(
  85. std::error_code( errno, std::system_category() ),
  86. "getcontext() failed");
  87. }
  88. #if defined(BOOST_USE_TSAN)
  89. tsan_fiber = __tsan_get_current_fiber();
  90. destroy_tsan_fiber = false;
  91. #endif
  92. }
  93. fiber_activation_record( stack_context sctx_) noexcept :
  94. sctx( sctx_ ),
  95. main_ctx( false ) {
  96. }
  97. virtual ~fiber_activation_record() {
  98. #if defined(BOOST_USE_TSAN)
  99. if (destroy_tsan_fiber)
  100. __tsan_destroy_fiber(tsan_fiber);
  101. #endif
  102. }
  103. fiber_activation_record( fiber_activation_record const&) = delete;
  104. fiber_activation_record & operator=( fiber_activation_record const&) = delete;
  105. bool is_main_context() const noexcept {
  106. return main_ctx;
  107. }
  108. fiber_activation_record * resume() {
  109. from = current();
  110. // store `this` in static, thread local pointer
  111. // `this` will become the active (running) context
  112. current() = this;
  113. #if defined(BOOST_USE_SEGMENTED_STACKS)
  114. // adjust segmented stack properties
  115. __splitstack_getcontext( from->sctx.segments_ctx);
  116. __splitstack_setcontext( sctx.segments_ctx);
  117. #endif
  118. #if defined(BOOST_USE_ASAN)
  119. if ( terminated) {
  120. __sanitizer_start_switch_fiber( nullptr, stack_bottom, stack_size);
  121. } else {
  122. __sanitizer_start_switch_fiber( & from->fake_stack, stack_bottom, stack_size);
  123. }
  124. #endif
  125. #if defined (BOOST_USE_TSAN)
  126. __tsan_switch_to_fiber(tsan_fiber, 0);
  127. #endif
  128. // context switch from parent context to `this`-context
  129. ::swapcontext( & from->uctx, & uctx);
  130. #if defined(BOOST_USE_ASAN)
  131. __sanitizer_finish_switch_fiber( current()->fake_stack,
  132. (const void **) & current()->from->stack_bottom,
  133. & current()->from->stack_size);
  134. #endif
  135. #if defined(BOOST_NO_CXX14_STD_EXCHANGE)
  136. return exchange( current()->from, nullptr);
  137. #else
  138. return std::exchange( current()->from, nullptr);
  139. #endif
  140. }
  141. template< typename Ctx, typename Fn >
  142. fiber_activation_record * resume_with( Fn && fn) {
  143. from = current();
  144. // store `this` in static, thread local pointer
  145. // `this` will become the active (running) context
  146. // returned by fiber::current()
  147. current() = this;
  148. #if defined(BOOST_NO_CXX14_GENERIC_LAMBDAS)
  149. current()->ontop = std::bind(
  150. [](typename std::decay< Fn >::type & fn, fiber_activation_record *& ptr){
  151. Ctx c{ ptr };
  152. c = fn( std::move( c) );
  153. if ( ! c) {
  154. ptr = nullptr;
  155. }
  156. #if defined(BOOST_NO_CXX14_STD_EXCHANGE)
  157. return exchange( c.ptr_, nullptr);
  158. #else
  159. return std::exchange( c.ptr_, nullptr);
  160. #endif
  161. },
  162. std::forward< Fn >( fn),
  163. std::placeholders::_1);
  164. #else
  165. current()->ontop = [fn=std::forward<Fn>(fn)](fiber_activation_record *& ptr){
  166. Ctx c{ ptr };
  167. c = fn( std::move( c) );
  168. if ( ! c) {
  169. ptr = nullptr;
  170. }
  171. #if defined(BOOST_NO_CXX14_STD_EXCHANGE)
  172. return exchange( c.ptr_, nullptr);
  173. #else
  174. return std::exchange( c.ptr_, nullptr);
  175. #endif
  176. };
  177. #endif
  178. #if defined(BOOST_USE_SEGMENTED_STACKS)
  179. // adjust segmented stack properties
  180. __splitstack_getcontext( from->sctx.segments_ctx);
  181. __splitstack_setcontext( sctx.segments_ctx);
  182. #endif
  183. #if defined(BOOST_USE_ASAN)
  184. __sanitizer_start_switch_fiber( & from->fake_stack, stack_bottom, stack_size);
  185. #endif
  186. #if defined (BOOST_USE_TSAN)
  187. __tsan_switch_to_fiber(tsan_fiber, 0);
  188. #endif
  189. // context switch from parent context to `this`-context
  190. ::swapcontext( & from->uctx, & uctx);
  191. #if defined(BOOST_USE_ASAN)
  192. __sanitizer_finish_switch_fiber( current()->fake_stack,
  193. (const void **) & current()->from->stack_bottom,
  194. & current()->from->stack_size);
  195. #endif
  196. #if defined(BOOST_NO_CXX14_STD_EXCHANGE)
  197. return exchange( current()->from, nullptr);
  198. #else
  199. return std::exchange( current()->from, nullptr);
  200. #endif
  201. }
  202. virtual void deallocate() noexcept {
  203. }
  204. };
  205. struct BOOST_CONTEXT_DECL fiber_activation_record_initializer {
  206. fiber_activation_record_initializer() noexcept;
  207. ~fiber_activation_record_initializer();
  208. };
  209. struct forced_unwind {
  210. fiber_activation_record * from{ nullptr };
  211. #ifndef BOOST_ASSERT_IS_VOID
  212. bool caught{ false };
  213. #endif
  214. forced_unwind( fiber_activation_record * from_) noexcept :
  215. from{ from_ } {
  216. }
  217. #ifndef BOOST_ASSERT_IS_VOID
  218. ~forced_unwind() {
  219. BOOST_ASSERT( caught);
  220. }
  221. #endif
  222. };
  223. template< typename Ctx, typename StackAlloc, typename Fn >
  224. class fiber_capture_record : public fiber_activation_record {
  225. private:
  226. typename std::decay< StackAlloc >::type salloc_;
  227. typename std::decay< Fn >::type fn_;
  228. static void destroy( fiber_capture_record * p) noexcept {
  229. typename std::decay< StackAlloc >::type salloc = std::move( p->salloc_);
  230. stack_context sctx = p->sctx;
  231. // deallocate activation record
  232. p->~fiber_capture_record();
  233. // destroy stack with stack allocator
  234. salloc.deallocate( sctx);
  235. }
  236. public:
  237. fiber_capture_record( stack_context sctx, StackAlloc && salloc, Fn && fn) noexcept :
  238. fiber_activation_record{ sctx },
  239. salloc_{ std::forward< StackAlloc >( salloc) },
  240. fn_( std::forward< Fn >( fn) ) {
  241. }
  242. void deallocate() noexcept override final {
  243. BOOST_ASSERT( main_ctx || ( ! main_ctx && terminated) );
  244. destroy( this);
  245. }
  246. void run() {
  247. #if defined(BOOST_USE_ASAN)
  248. __sanitizer_finish_switch_fiber( fake_stack,
  249. (const void **) & from->stack_bottom,
  250. & from->stack_size);
  251. #endif
  252. Ctx c{ from };
  253. try {
  254. // invoke context-function
  255. #if defined(BOOST_NO_CXX17_STD_INVOKE)
  256. c = boost::context::detail::invoke( fn_, std::move( c) );
  257. #else
  258. c = std::invoke( fn_, std::move( c) );
  259. #endif
  260. } catch ( forced_unwind const& ex) {
  261. c = Ctx{ ex.from };
  262. #ifndef BOOST_ASSERT_IS_VOID
  263. const_cast< forced_unwind & >( ex).caught = true;
  264. #endif
  265. }
  266. // this context has finished its task
  267. from = nullptr;
  268. ontop = nullptr;
  269. terminated = true;
  270. force_unwind = false;
  271. std::move( c).resume();
  272. BOOST_ASSERT_MSG( false, "fiber already terminated");
  273. }
  274. };
  275. template< typename Ctx, typename StackAlloc, typename Fn >
  276. static fiber_activation_record * create_fiber1( StackAlloc && salloc, Fn && fn) {
  277. typedef fiber_capture_record< Ctx, StackAlloc, Fn > capture_t;
  278. auto sctx = salloc.allocate();
  279. // reserve space for control structure
  280. void * storage = reinterpret_cast< void * >(
  281. ( reinterpret_cast< uintptr_t >( sctx.sp) - static_cast< uintptr_t >( sizeof( capture_t) ) )
  282. & ~ static_cast< uintptr_t >( 0xff) );
  283. // placment new for control structure on context stack
  284. capture_t * record = new ( storage) capture_t{
  285. sctx, std::forward< StackAlloc >( salloc), std::forward< Fn >( fn) };
  286. // stack bottom
  287. void * stack_bottom = reinterpret_cast< void * >(
  288. reinterpret_cast< uintptr_t >( sctx.sp) - static_cast< uintptr_t >( sctx.size) );
  289. // create user-context
  290. if ( BOOST_UNLIKELY( 0 != ::getcontext( & record->uctx) ) ) {
  291. record->~capture_t();
  292. salloc.deallocate( sctx);
  293. throw std::system_error(
  294. std::error_code( errno, std::system_category() ),
  295. "getcontext() failed");
  296. }
  297. record->uctx.uc_stack.ss_sp = stack_bottom;
  298. // 64byte gap between control structure and stack top
  299. record->uctx.uc_stack.ss_size = reinterpret_cast< uintptr_t >( storage) -
  300. reinterpret_cast< uintptr_t >( stack_bottom) - static_cast< uintptr_t >( 64);
  301. record->uctx.uc_link = nullptr;
  302. ::makecontext( & record->uctx, ( void (*)() ) & fiber_entry_func< capture_t >, 1, record);
  303. #if defined(BOOST_USE_ASAN)
  304. record->stack_bottom = record->uctx.uc_stack.ss_sp;
  305. record->stack_size = record->uctx.uc_stack.ss_size;
  306. #endif
  307. #if defined (BOOST_USE_TSAN)
  308. record->tsan_fiber = __tsan_create_fiber(0);
  309. #endif
  310. return record;
  311. }
  312. template< typename Ctx, typename StackAlloc, typename Fn >
  313. static fiber_activation_record * create_fiber2( preallocated palloc, StackAlloc && salloc, Fn && fn) {
  314. typedef fiber_capture_record< Ctx, StackAlloc, Fn > capture_t;
  315. // reserve space for control structure
  316. void * storage = reinterpret_cast< void * >(
  317. ( reinterpret_cast< uintptr_t >( palloc.sp) - static_cast< uintptr_t >( sizeof( capture_t) ) )
  318. & ~ static_cast< uintptr_t >( 0xff) );
  319. // placment new for control structure on context stack
  320. capture_t * record = new ( storage) capture_t{
  321. palloc.sctx, std::forward< StackAlloc >( salloc), std::forward< Fn >( fn) };
  322. // stack bottom
  323. void * stack_bottom = reinterpret_cast< void * >(
  324. reinterpret_cast< uintptr_t >( palloc.sctx.sp) - static_cast< uintptr_t >( palloc.sctx.size) );
  325. // create user-context
  326. if ( BOOST_UNLIKELY( 0 != ::getcontext( & record->uctx) ) ) {
  327. record->~capture_t();
  328. salloc.deallocate( palloc.sctx);
  329. throw std::system_error(
  330. std::error_code( errno, std::system_category() ),
  331. "getcontext() failed");
  332. }
  333. record->uctx.uc_stack.ss_sp = stack_bottom;
  334. // 64byte gap between control structure and stack top
  335. record->uctx.uc_stack.ss_size = reinterpret_cast< uintptr_t >( storage) -
  336. reinterpret_cast< uintptr_t >( stack_bottom) - static_cast< uintptr_t >( 64);
  337. record->uctx.uc_link = nullptr;
  338. ::makecontext( & record->uctx, ( void (*)() ) & fiber_entry_func< capture_t >, 1, record);
  339. #if defined(BOOST_USE_ASAN)
  340. record->stack_bottom = record->uctx.uc_stack.ss_sp;
  341. record->stack_size = record->uctx.uc_stack.ss_size;
  342. #endif
  343. #if defined (BOOST_USE_TSAN)
  344. record->tsan_fiber = __tsan_create_fiber(0);
  345. #endif
  346. return record;
  347. }
  348. }
  349. class BOOST_CONTEXT_DECL fiber {
  350. private:
  351. friend struct detail::fiber_activation_record;
  352. template< typename Ctx, typename StackAlloc, typename Fn >
  353. friend class detail::fiber_capture_record;
  354. template< typename Ctx, typename StackAlloc, typename Fn >
  355. friend detail::fiber_activation_record * detail::create_fiber1( StackAlloc &&, Fn &&);
  356. template< typename Ctx, typename StackAlloc, typename Fn >
  357. friend detail::fiber_activation_record * detail::create_fiber2( preallocated, StackAlloc &&, Fn &&);
  358. template< typename StackAlloc, typename Fn >
  359. friend fiber
  360. callcc( std::allocator_arg_t, StackAlloc &&, Fn &&);
  361. template< typename StackAlloc, typename Fn >
  362. friend fiber
  363. callcc( std::allocator_arg_t, preallocated, StackAlloc &&, Fn &&);
  364. detail::fiber_activation_record * ptr_{ nullptr };
  365. fiber( detail::fiber_activation_record * ptr) noexcept :
  366. ptr_{ ptr } {
  367. }
  368. public:
  369. fiber() = default;
  370. template< typename Fn, typename = detail::disable_overload< fiber, Fn > >
  371. fiber( Fn && fn) :
  372. fiber{
  373. std::allocator_arg,
  374. #if defined(BOOST_USE_SEGMENTED_STACKS)
  375. segmented_stack(),
  376. #else
  377. fixedsize_stack(),
  378. #endif
  379. std::forward< Fn >( fn) } {
  380. }
  381. template< typename StackAlloc, typename Fn >
  382. fiber( std::allocator_arg_t, StackAlloc && salloc, Fn && fn) :
  383. ptr_{ detail::create_fiber1< fiber >(
  384. std::forward< StackAlloc >( salloc), std::forward< Fn >( fn) ) } {
  385. }
  386. template< typename StackAlloc, typename Fn >
  387. fiber( std::allocator_arg_t, preallocated palloc, StackAlloc && salloc, Fn && fn) :
  388. ptr_{ detail::create_fiber2< fiber >(
  389. palloc, std::forward< StackAlloc >( salloc), std::forward< Fn >( fn) ) } {
  390. }
  391. ~fiber() {
  392. if ( BOOST_UNLIKELY( nullptr != ptr_) && ! ptr_->main_ctx) {
  393. if ( BOOST_LIKELY( ! ptr_->terminated) ) {
  394. ptr_->force_unwind = true;
  395. ptr_->resume();
  396. BOOST_ASSERT( ptr_->terminated);
  397. }
  398. ptr_->deallocate();
  399. }
  400. }
  401. fiber( fiber const&) = delete;
  402. fiber & operator=( fiber const&) = delete;
  403. fiber( fiber && other) noexcept {
  404. swap( other);
  405. }
  406. fiber & operator=( fiber && other) noexcept {
  407. if ( BOOST_LIKELY( this != & other) ) {
  408. fiber tmp = std::move( other);
  409. swap( tmp);
  410. }
  411. return * this;
  412. }
  413. fiber resume() && {
  414. BOOST_ASSERT( nullptr != ptr_);
  415. #if defined(BOOST_NO_CXX14_STD_EXCHANGE)
  416. detail::fiber_activation_record * ptr = detail::exchange( ptr_, nullptr)->resume();
  417. #else
  418. detail::fiber_activation_record * ptr = std::exchange( ptr_, nullptr)->resume();
  419. #endif
  420. if ( BOOST_UNLIKELY( detail::fiber_activation_record::current()->force_unwind) ) {
  421. throw detail::forced_unwind{ ptr};
  422. } else if ( BOOST_UNLIKELY( nullptr != detail::fiber_activation_record::current()->ontop) ) {
  423. ptr = detail::fiber_activation_record::current()->ontop( ptr);
  424. detail::fiber_activation_record::current()->ontop = nullptr;
  425. }
  426. return { ptr };
  427. }
  428. template< typename Fn >
  429. fiber resume_with( Fn && fn) && {
  430. BOOST_ASSERT( nullptr != ptr_);
  431. #if defined(BOOST_NO_CXX14_STD_EXCHANGE)
  432. detail::fiber_activation_record * ptr =
  433. detail::exchange( ptr_, nullptr)->resume_with< fiber >( std::forward< Fn >( fn) );
  434. #else
  435. detail::fiber_activation_record * ptr =
  436. std::exchange( ptr_, nullptr)->resume_with< fiber >( std::forward< Fn >( fn) );
  437. #endif
  438. if ( BOOST_UNLIKELY( detail::fiber_activation_record::current()->force_unwind) ) {
  439. throw detail::forced_unwind{ ptr};
  440. } else if ( BOOST_UNLIKELY( nullptr != detail::fiber_activation_record::current()->ontop) ) {
  441. ptr = detail::fiber_activation_record::current()->ontop( ptr);
  442. detail::fiber_activation_record::current()->ontop = nullptr;
  443. }
  444. return { ptr };
  445. }
  446. explicit operator bool() const noexcept {
  447. return nullptr != ptr_ && ! ptr_->terminated;
  448. }
  449. bool operator!() const noexcept {
  450. return nullptr == ptr_ || ptr_->terminated;
  451. }
  452. bool operator<( fiber const& other) const noexcept {
  453. return ptr_ < other.ptr_;
  454. }
  455. #if !defined(BOOST_EMBTC)
  456. template< typename charT, class traitsT >
  457. friend std::basic_ostream< charT, traitsT > &
  458. operator<<( std::basic_ostream< charT, traitsT > & os, fiber const& other) {
  459. if ( nullptr != other.ptr_) {
  460. return os << other.ptr_;
  461. } else {
  462. return os << "{not-a-context}";
  463. }
  464. }
  465. #else
  466. template< typename charT, class traitsT >
  467. friend std::basic_ostream< charT, traitsT > &
  468. operator<<( std::basic_ostream< charT, traitsT > & os, fiber const& other);
  469. #endif
  470. void swap( fiber & other) noexcept {
  471. std::swap( ptr_, other.ptr_);
  472. }
  473. };
  474. #if defined(BOOST_EMBTC)
  475. template< typename charT, class traitsT >
  476. inline std::basic_ostream< charT, traitsT > &
  477. operator<<( std::basic_ostream< charT, traitsT > & os, fiber const& other) {
  478. if ( nullptr != other.ptr_) {
  479. return os << other.ptr_;
  480. } else {
  481. return os << "{not-a-context}";
  482. }
  483. }
  484. #endif
  485. inline
  486. void swap( fiber & l, fiber & r) noexcept {
  487. l.swap( r);
  488. }
  489. typedef fiber fiber_context;
  490. }}
  491. #ifdef BOOST_HAS_ABI_HEADERS
  492. # include BOOST_ABI_SUFFIX
  493. #endif
  494. #endif // BOOST_CONTEXT_FIBER_H