libcopp  2.2.0
coroutine_context.cpp
Go to the documentation of this file.
1 // Copyright 2023 owent
2 
3 #include <libcopp/utils/config/libcopp_build_features.h>
4 
5 #include <libcopp/utils/errno.h>
7 
9 
10 // clang-format off
11 #include <libcopp/utils/config/stl_include_prefix.h> // NOLINT(build/include_order)
12 // clang-format on
13 #if defined(COPP_MACRO_THREAD_LOCAL)
14 // using thread_local
15 #else
16 # include <pthread.h>
17 #endif
18 #include <assert.h>
19 #include <algorithm>
20 #include <cstdlib>
21 #include <cstring>
22 // clang-format off
23 #include <libcopp/utils/config/stl_include_suffix.h> // NOLINT(build/include_order)
24 // clang-format on
25 
26 #ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
27 extern "C" {
28 void __splitstack_getcontext(void *[COPP_MACRO_SEGMENTED_STACK_NUMBER]);
29 
30 void __splitstack_setcontext(void *[COPP_MACRO_SEGMENTED_STACK_NUMBER]);
31 
32 void __splitstack_releasecontext(void *[COPP_MACRO_SEGMENTED_STACK_NUMBER]);
33 
34 void __splitstack_block_signals_context(void *[COPP_MACRO_SEGMENTED_STACK_NUMBER], int *, int *);
35 }
36 #endif
37 
38 LIBCOPP_COPP_NAMESPACE_BEGIN
39 namespace detail {
40 
41 #if defined(LIBCOPP_LOCK_DISABLE_THIS_MT) && LIBCOPP_LOCK_DISABLE_THIS_MT
42 static coroutine_context_base *gt_current_coroutine = nullptr;
43 #elif defined(COPP_MACRO_THREAD_LOCAL)
44 static COPP_MACRO_THREAD_LOCAL coroutine_context_base *gt_current_coroutine = nullptr;
45 #else
46 static pthread_once_t gt_coroutine_init_once = PTHREAD_ONCE_INIT;
47 static pthread_key_t gt_coroutine_tls_key;
48 static void init_pthread_this_coroutine_context() { (void)pthread_key_create(&gt_coroutine_tls_key, nullptr); }
49 #endif
50 
52 #if (defined(LIBCOPP_LOCK_DISABLE_THIS_MT) && LIBCOPP_LOCK_DISABLE_THIS_MT) || defined(COPP_MACRO_THREAD_LOCAL)
53  gt_current_coroutine = p;
54 #else
56  pthread_setspecific(gt_coroutine_tls_key, p);
57 #endif
58 }
59 
61 #if (defined(LIBCOPP_LOCK_DISABLE_THIS_MT) && LIBCOPP_LOCK_DISABLE_THIS_MT) || defined(COPP_MACRO_THREAD_LOCAL)
62  return gt_current_coroutine;
63 #else
65  return reinterpret_cast<coroutine_context_base *>(pthread_getspecific(gt_coroutine_tls_key));
66 #endif
67 }
68 } // namespace detail
69 
70 LIBCOPP_COPP_API coroutine_context_base::coroutine_context_base() LIBCOPP_MACRO_NOEXCEPT
71  : runner_ret_code_(0),
72  flags_(0),
73  runner_(nullptr),
74  priv_data_(nullptr),
75  private_buffer_size_(0),
76  status_(status_type::EN_CRS_INVALID) {}
77 
79 
80 LIBCOPP_COPP_API bool coroutine_context_base::set_flags(int flags) LIBCOPP_MACRO_NOEXCEPT {
81  if (flags & flag_type::EN_CFT_MASK) {
82  return false;
83  }
84 
85  flags_ |= flags;
86  return true;
87 }
88 
89 LIBCOPP_COPP_API bool coroutine_context_base::unset_flags(int flags) LIBCOPP_MACRO_NOEXCEPT {
90  if (flags & flag_type::EN_CFT_MASK) {
91  return false;
92  }
93 
94  flags_ &= ~flags;
95  return true;
96 }
97 
98 LIBCOPP_COPP_API bool coroutine_context_base::check_flags(int flags) const LIBCOPP_MACRO_NOEXCEPT {
99  return 0 != (flags_ & flags);
100 }
101 
102 LIBCOPP_COPP_API int coroutine_context_base::set_runner(callback_type &&runner) {
103  if (!runner) {
104  return COPP_EC_ARGS_ERROR;
105  }
106 
107  int from_status = status_type::EN_CRS_INVALID;
108  if (false == status_.compare_exchange_strong(from_status, status_type::EN_CRS_READY,
111  return COPP_EC_ALREADY_INITED;
112  }
113 
114  runner_ = std::move(runner);
115  return COPP_EC_SUCCESS;
116 }
117 
118 LIBCOPP_COPP_API bool coroutine_context_base::is_finished() const LIBCOPP_MACRO_NOEXCEPT {
119  // return !!(flags_ & flag_type::EN_CFT_FINISHED);
121 }
122 
125 }
126 
128  LIBCOPP_MACRO_NOEXCEPT {
130 }
131 
134 
136  if (nullptr != src) {
137  src->caller_ = fctx;
138  }
139  }
140 
142  if (nullptr != src) {
143  src->callee_ = fctx;
144  }
145  }
146 
147 #ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
148  UTIL_FORCEINLINE static void splitstack_swapcontext(EXPLICIT_UNUSED_ATTR stack_context &from_sctx,
151  if (nullptr != jump_transfer.from_co) {
152  __splitstack_getcontext(jump_transfer.from_co->callee_stack_.segments_ctx);
153  if (&from_sctx != &jump_transfer.from_co->callee_stack_) {
154  memcpy(&from_sctx.segments_ctx, &jump_transfer.from_co->callee_stack_.segments_ctx,
155  sizeof(from_sctx.segments_ctx));
156  }
157  } else {
158  __splitstack_getcontext(from_sctx.segments_ctx);
159  }
160  __splitstack_setcontext(to_sctx.segments_ctx);
161  }
162 #endif
163 
164  static void coroutine_context_callback(LIBCOPP_COPP_NAMESPACE_ID::fcontext::transfer_t src_ctx) {
165  assert(src_ctx.data);
166  if (nullptr == src_ctx.data) {
167  abort();
168  // return; // clang-analyzer will report "Unreachable code"
169  }
170 
171  // copy jump_src_data_t in case it's destroyed later
172  jump_src_data_t jump_src = *reinterpret_cast<jump_src_data_t *>(src_ctx.data);
173 
174  // this must in a coroutine
175  coroutine_context *ins_ptr = jump_src.to_co;
176  assert(ins_ptr);
177  if (nullptr == ins_ptr) {
178  abort();
179  // return; // clang-analyzer will report "Unreachable code"
180  }
181 
182  // update caller of to_co
183  ins_ptr->caller_ = src_ctx.fctx;
184 
185  // save from_co's fcontext and switch status
186  if (nullptr != jump_src.from_co) {
187  jump_src.from_co->callee_ = src_ctx.fctx;
188  }
189 
190  // this_coroutine
192 
193  // run logic code
194 #if defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
195  try {
196 #endif
197  ins_ptr->run_and_recv_retcode(jump_src.priv_data);
198 #if defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
199  } catch (...) {
200  ins_ptr->unhandle_exception_ = std::current_exception();
201  }
202 #endif
203 
205  // add memory fence to flush flags_(used in is_finished())
206  // LIBCOPP_UTIL_LOCK_ATOMIC_THREAD_FENCE(LIBCOPP_COPP_NAMESPACE_ID::util::lock::memory_order_release);
207 
208  // jump back to caller
209  ins_ptr->yield();
210  }
211 };
219 static inline void jump_to(fcontext::fcontext_t &to_fctx, EXPLICIT_UNUSED_ATTR stack_context &from_sctx,
221  libcopp_internal_api_set::jump_src_data_t &jump_transfer) LIBCOPP_MACRO_NOEXCEPT {
222  LIBCOPP_COPP_NAMESPACE_ID::fcontext::transfer_t res;
224  // int from_status;
225  // bool swap_success;
226  // can not use any more stack now
227  // can not initialize those vars here
228 
229 #ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
230  assert(&from_sctx != &to_sctx);
231  // ROOT->A: jump_transfer.from_co == nullptr, jump_transfer.to_co == A, from_sctx == A.caller_stack_, skip
232  // backup segments A->B.start(): jump_transfer.from_co == A, jump_transfer.to_co == B, from_sctx == B.caller_stack_,
233  // backup segments B.yield()->A: jump_transfer.from_co == B, jump_transfer.to_co == nullptr, from_sctx ==
234  // B.callee_stack_, skip backup segments
235  libcopp_internal_api_set::splitstack_swapcontext(from_sctx, to_sctx, jump_transfer);
236 #endif
237  res = LIBCOPP_COPP_NAMESPACE_ID::fcontext::copp_jump_fcontext_v2(to_fctx, &jump_transfer);
238  if (nullptr == res.data) {
239  abort();
240  return;
241  }
242  jump_src = reinterpret_cast<libcopp_internal_api_set::jump_src_data_t *>(res.data);
243  assert(jump_src);
244 
259  // update caller of to_co if not jump from yield mode
260  libcopp_internal_api_set::set_caller(jump_src->to_co, res.fctx);
261 
262  libcopp_internal_api_set::set_callee(jump_src->from_co, res.fctx);
263 
264  // private data
265  jump_transfer.priv_data = jump_src->priv_data;
266 
267  // this_coroutine
269 }
270 
271 LIBCOPP_COPP_API coroutine_context::coroutine_context() LIBCOPP_MACRO_NOEXCEPT : coroutine_context_base(),
272  caller_(nullptr),
273  callee_(nullptr),
274  callee_stack_()
275 #ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
276  ,
277  caller_stack_()
278 #endif
279 {
280 }
281 
283 
285  const stack_context &callee_stack, size_t coroutine_size,
286  size_t private_buffer_size) LIBCOPP_MACRO_NOEXCEPT {
287  if (nullptr == p) {
288  return COPP_EC_ARGS_ERROR;
289  }
290 
291  // must aligned to sizeof(size_t)
292  if (0 != (private_buffer_size & (sizeof(size_t) - 1))) {
293  return COPP_EC_ARGS_ERROR;
294  }
295 
296  if (0 != (coroutine_size & (sizeof(size_t) - 1))) {
297  return COPP_EC_ARGS_ERROR;
298  }
299 
300  size_t stack_offset = align_stack_size(private_buffer_size + coroutine_size);
301  if (nullptr == callee_stack.sp || callee_stack.size <= stack_offset) {
302  return COPP_EC_ARGS_ERROR;
303  }
304 
305  // stack down
306  // |STACK BUFFER........COROUTINE..this..padding..PRIVATE DATA.....callee_stack.sp |
307  // |------------------------------callee_stack.size -------------------------------|
308  if (callee_stack.sp <= p || coroutine_size < sizeof(coroutine_context)) {
309  return COPP_EC_ARGS_ERROR;
310  }
311 
312  size_t this_offset = reinterpret_cast<unsigned char *>(callee_stack.sp) - reinterpret_cast<unsigned char *>(p);
313  if (this_offset < sizeof(coroutine_context) + private_buffer_size || this_offset > stack_offset) {
314  return COPP_EC_ARGS_ERROR;
315  }
316 
317  // if runner is empty, we can set it later
318  p->set_runner(std::move(runner));
319 
320  if (&p->callee_stack_ != &callee_stack) {
321  p->callee_stack_ = callee_stack;
322  }
323  p->private_buffer_size_ = private_buffer_size;
324 
325  // stack down, left enough private data
326  p->priv_data_ = reinterpret_cast<unsigned char *>(p->callee_stack_.sp) - p->private_buffer_size_;
327  p->callee_ = fcontext::copp_make_fcontext_v2(reinterpret_cast<unsigned char *>(p->callee_stack_.sp) - stack_offset,
328  p->callee_stack_.size - stack_offset,
330  if (nullptr == p->callee_) {
332  }
333 
334  return COPP_EC_SUCCESS;
335 }
336 
337 #if defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
338 LIBCOPP_COPP_API int coroutine_context::start(void *priv_data) {
339  std::exception_ptr eptr;
340  int ret = start(eptr, priv_data);
341  maybe_rethrow(eptr);
342  return ret;
343 }
344 
345 LIBCOPP_COPP_API int coroutine_context::start(std::exception_ptr &unhandled, void *priv_data) LIBCOPP_MACRO_NOEXCEPT {
346 #else
347 LIBCOPP_COPP_API int coroutine_context::start(void *priv_data) {
348 #endif
349  if (nullptr == callee_) {
350  return COPP_EC_NOT_INITED;
351  }
352 
353 #if defined(LIBCOPP_MACRO_ENABLE_WIN_FIBER) && LIBCOPP_MACRO_ENABLE_WIN_FIBER
354  {
356  if (this_ctx && this_ctx->check_flags(flag_type::EN_CFT_IS_FIBER)) {
358  }
359  }
360 #endif
361 
362  int from_status = status_type::EN_CRS_READY;
363  do {
364  if (from_status < status_type::EN_CRS_READY) {
365  return COPP_EC_NOT_INITED;
366  }
367 
368  if (status_.compare_exchange_strong(from_status, status_type::EN_CRS_RUNNING,
371  break;
372  } else {
373  // finished or stoped
374  if (from_status > status_type::EN_CRS_RUNNING) {
375  return COPP_EC_NOT_READY;
376  }
377 
378  // already running
379  if (status_type::EN_CRS_RUNNING == from_status) {
380  return COPP_EC_IS_RUNNING;
381  }
382  }
383  } while (true);
384 
385  jump_src_data_t jump_data;
386 #if defined(LIBCOPP_MACRO_ENABLE_WIN_FIBER) && LIBCOPP_MACRO_ENABLE_WIN_FIBER
388 #else
389  jump_data.from_co = static_cast<coroutine_context *>(detail::get_this_coroutine_context());
390 #endif
391  jump_data.to_co = this;
392  jump_data.priv_data = priv_data;
393 
394 #ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
395  jump_to(callee_, caller_stack_, callee_stack_, jump_data);
396 #else
398 #endif
399 
400  // Move changing status to EN_CRS_EXITED is finished
402  // if in finished status, change it to exited
404  }
405 
406 #if defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
407  COPP_UNLIKELY_IF (unhandle_exception_) {
408  std::swap(unhandled, unhandle_exception_);
409  }
410 #endif
411 
412  return COPP_EC_SUCCESS;
413 }
414 
415 LIBCOPP_COPP_API int coroutine_context::resume(void *priv_data) { return start(priv_data); }
416 #if defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
417 LIBCOPP_COPP_API int coroutine_context::resume(std::exception_ptr &unhandled, void *priv_data) LIBCOPP_MACRO_NOEXCEPT {
418  return start(unhandled, priv_data);
419 }
420 #endif
421 
422 LIBCOPP_COPP_API int coroutine_context::yield(void **priv_data) LIBCOPP_MACRO_NOEXCEPT {
423  if (nullptr == callee_) {
424  return COPP_EC_NOT_INITED;
425  }
426 
427  int from_status = status_type::EN_CRS_RUNNING;
428  int to_status = status_type::EN_CRS_READY;
429  if (check_flags(flag_type::EN_CFT_FINISHED)) {
430  to_status = status_type::EN_CRS_FINISHED;
431  }
432  if (false == status_.compare_exchange_strong(from_status, to_status,
435  switch (from_status) {
436  case status_type::EN_CRS_INVALID:
437  return COPP_EC_NOT_INITED;
438  case status_type::EN_CRS_READY:
439  return COPP_EC_NOT_RUNNING;
440  case status_type::EN_CRS_FINISHED:
441  case status_type::EN_CRS_EXITED:
442  return COPP_EC_ALREADY_EXIST;
443  default:
444  return COPP_EC_UNKNOWN;
445  }
446  }
447 
448  // success or finished will continue
449  jump_src_data_t jump_data;
450  jump_data.from_co = this;
451  jump_data.to_co = nullptr;
452 
453 #ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
454  jump_to(caller_, callee_stack_, caller_stack_, jump_data);
455 #else
456  jump_to(caller_, callee_stack_, callee_stack_, jump_data);
457 #endif
458 
459  if (nullptr != priv_data) {
460  *priv_data = jump_data.priv_data;
461  }
462 
463  return COPP_EC_SUCCESS;
464 }
465 
466 namespace this_coroutine {
467 LIBCOPP_COPP_API coroutine_context *get_coroutine() LIBCOPP_MACRO_NOEXCEPT {
469 #if defined(LIBCOPP_MACRO_ENABLE_WIN_FIBER) && LIBCOPP_MACRO_ENABLE_WIN_FIBER
471  ret = nullptr;
472  }
473 #endif
474  return static_cast<coroutine_context *>(ret);
475 }
476 
477 LIBCOPP_COPP_API int yield(void **priv_data) LIBCOPP_MACRO_NOEXCEPT {
478 #if defined(LIBCOPP_MACRO_ENABLE_WIN_FIBER) && LIBCOPP_MACRO_ENABLE_WIN_FIBER
480 #else
482 #endif
483  COPP_LIKELY_IF (nullptr != pco) {
484  return pco->yield(priv_data);
485  }
486 
487  return COPP_EC_NOT_RUNNING;
488 }
489 } // namespace this_coroutine
490 LIBCOPP_COPP_NAMESPACE_END
base type of all coroutine context
LIBCOPP_COPP_API ~coroutine_context_base()
UTIL_FORCEINLINE void run_and_recv_retcode(void *priv_data)
coroutine entrance function
LIBCOPP_COPP_API bool set_flags(int flags) LIBCOPP_MACRO_NOEXCEPT
set all flags to true
LIBCOPP_COPP_API int set_runner(callback_type &&runner)
set runner
static LIBCOPP_COPP_API void set_this_coroutine_base(coroutine_context_base *ctx) LIBCOPP_MACRO_NOEXCEPT
set current coroutine
static LIBCOPP_COPP_API coroutine_context_base * get_this_coroutine_base() LIBCOPP_MACRO_NOEXCEPT
get current coroutine
LIBCOPP_COPP_API bool check_flags(int flags) const LIBCOPP_MACRO_NOEXCEPT
check flags
LIBCOPP_COPP_API coroutine_context_base() LIBCOPP_MACRO_NOEXCEPT
LIBCOPP_COPP_NAMESPACE_ID::util::lock::atomic_int_type< int > status_
LIBCOPP_COPP_API bool is_finished() const LIBCOPP_MACRO_NOEXCEPT
get runner return code
LIBCOPP_COPP_API bool unset_flags(int flags) LIBCOPP_MACRO_NOEXCEPT
set all flags to false
std::function< int(void *)> callback_type
base type of all stackful coroutine context
LIBCOPP_COPP_API coroutine_context() LIBCOPP_MACRO_NOEXCEPT
fcontext::fcontext_t caller_
stack_context callee_stack_
LIBCOPP_COPP_API int start(void *priv_data=nullptr)
start coroutine
LIBCOPP_COPP_API int yield(void **priv_data=nullptr) LIBCOPP_MACRO_NOEXCEPT
yield coroutine
LIBCOPP_COPP_API ~coroutine_context()
LIBCOPP_COPP_API int resume(void *priv_data=nullptr)
resume coroutine
LIBCOPP_COPP_NAMESPACE_ID::util::lock::atomic_int_type< int > status_
fcontext::fcontext_t callee_
coroutine_context_base::callback_type callback_type
static LIBCOPP_COPP_API int create(coroutine_context *p, callback_type &&runner, const stack_context &callee_stack, size_t coroutine_size, size_t private_buffer_size) LIBCOPP_MACRO_NOEXCEPT
create coroutine context at stack context callee_
#define UTIL_FORCEINLINE
static void jump_to(fcontext::fcontext_t &to_fctx, EXPLICIT_UNUSED_ATTR stack_context &from_sctx, EXPLICIT_UNUSED_ATTR stack_context &to_sctx, libcopp_internal_api_set::jump_src_data_t &jump_transfer) LIBCOPP_MACRO_NOEXCEPT
call platform jump to asm instruction
@ COPP_EC_CAN_NOT_USE_CROSS_FCONTEXT_AND_FIBER
COPP_EC_CAN_NOT_USE_CROSS_FCONTEXT_AND_FIBER.
Definition: errno.h:35
@ COPP_EC_SUCCESS
COPP_EC_SUCCESS.
Definition: errno.h:12
@ COPP_EC_IS_RUNNING
COPP_EC_IS_RUNNING.
Definition: errno.h:26
@ COPP_EC_FCONTEXT_MAKE_FAILED
COPP_EC_FCONTEXT_MAKE_FAILED.
Definition: errno.h:34
@ COPP_EC_ALREADY_INITED
COPP_EC_ALREADY_INITED.
Definition: errno.h:22
@ COPP_EC_NOT_READY
COPP_EC_NOT_READY.
Definition: errno.h:24
@ COPP_EC_NOT_INITED
COPP_EC_NOT_INITED.
Definition: errno.h:21
@ COPP_EC_UNKNOWN
COPP_EC_UNKNOWN.
Definition: errno.h:14
@ COPP_EC_ALREADY_EXIST
COPP_EC_ALREADY_EXIST.
Definition: errno.h:29
@ COPP_EC_ARGS_ERROR
COPP_EC_ARGS_ERROR.
Definition: errno.h:30
@ COPP_EC_NOT_RUNNING
COPP_EC_NOT_RUNNING.
Definition: errno.h:25
导入继承关系约束 Licensed under the MIT licenses.
#define EXPLICIT_UNUSED_ATTR
maybe_unused, 标记忽略unused警告 usage: EXPLICIT_UNUSED_ATTR int a; class EXPLICIT_UNUSED_ATTR a; EXPLICIT_...
#define COPP_UNLIKELY_IF(...)
Definition: features.h:117
#define COPP_LIKELY_IF(...)
Definition: features.h:102
static void set_this_coroutine_context(coroutine_context_base *p)
static pthread_key_t gt_coroutine_tls_key
static void init_pthread_this_coroutine_context()
static coroutine_context_base * get_this_coroutine_context()
static pthread_once_t gt_coroutine_init_once
COPP_BOOST_CONTEXT_DECL fcontext_t COPP_BOOST_CONTEXT_CALLDECL copp_make_fcontext_v2(void *sp, std::size_t size, void(*fn)(transfer_t))
COPP_BOOST_CONTEXT_DECL transfer_t COPP_BOOST_CONTEXT_CALLDECL copp_jump_fcontext_v2(fcontext_t const to, void *vp)
LIBCOPP_COPP_API int yield(void **priv_data=nullptr) LIBCOPP_MACRO_NOEXCEPT
yield current coroutine
LIBCOPP_COPP_API coroutine_context * get_coroutine() LIBCOPP_MACRO_NOEXCEPT
get current coroutine
void swap(intrusive_ptr< T > &lhs, intrusive_ptr< T > &rhs)
status of safe coroutine context base
static void coroutine_context_callback(LIBCOPP_COPP_NAMESPACE_ID::fcontext::transfer_t src_ctx)
static UTIL_FORCEINLINE void set_caller(coroutine_context *src, const fcontext::fcontext_t &fctx)
static UTIL_FORCEINLINE void set_callee(coroutine_context *src, const fcontext::fcontext_t &fctx)