fiber_ucontext.hpp 18 KB

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