libcopp  1.2.1
coroutine_context.cpp
Go to the documentation of this file.
1 #include <algorithm>
2 #include <assert.h>
3 #include <cstdlib>
4 #include <cstring>
5 
8 #include <libcopp/utils/errno.h>
10 
12 
13 #if defined(UTIL_CONFIG_THREAD_LOCAL)
14 // using thread_local
15 #else
16 #include <pthread.h>
17 #endif
18 
19 // ================ import/export: for compilers ================
20 #if defined(__INTEL_COMPILER) || defined(__ICL) || defined(__ICC) || defined(__ECC)
21 // Intel
22 //
23 // Dynamic shared object (DSO) and dynamic-link library (DLL) support
24 //
25 #if defined(__GNUC__) && (__GNUC__ >= 4)
26 #define LIBCOPP_SYMBOL_LOCAL __attribute__((visibility("hidden")))
27 #endif
28 
29 #elif defined __clang__ && !defined(__CUDACC__) && !defined(__ibmxl__)
30 // when using clang and cuda at same time, you want to appear as gcc
31 // Clang C++ emulates GCC, so it has to appear early.
32 //
33 
34 // Dynamic shared object (DSO) and dynamic-link library (DLL) support
35 //
36 #if !defined(_WIN32) && !defined(__WIN32__) && !defined(WIN32)
37 #define LIBCOPP_SYMBOL_LOCAL __attribute__((__visibility__("hidden")))
38 #endif
39 
40 #elif defined(__GNUC__) && !defined(__ibmxl__)
41 // GNU C++:
42 //
43 // Dynamic shared object (DSO) and dynamic-link library (DLL) support
44 //
45 #if __GNUC__ >= 4
46 #if (defined(_WIN32) || defined(__WIN32__) || defined(WIN32)) && !defined(__CYGWIN__)
47 // NONE
48 #else
49 #define LIBCOPP_SYMBOL_LOCAL __attribute__((__visibility__("hidden")))
50 #endif
51 #endif
52 #endif
53 
54 #ifndef LIBCOPP_SYMBOL_LOCAL
55 #define LIBCOPP_SYMBOL_LOCAL
56 #endif
57 
58 #ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
59 extern "C" {
60 void __splitstack_getcontext(void * [COPP_MACRO_SEGMENTED_STACK_NUMBER]);
61 
62 void __splitstack_setcontext(void * [COPP_MACRO_SEGMENTED_STACK_NUMBER]);
63 
64 void __splitstack_releasecontext(void * [COPP_MACRO_SEGMENTED_STACK_NUMBER]);
65 
66 void __splitstack_block_signals_context(void * [COPP_MACRO_SEGMENTED_STACK_NUMBER], int *, int *);
67 }
68 #endif
69 
70 namespace copp {
71  namespace detail {
72 
73 #if defined(LIBCOPP_DISABLE_THIS_MT) && LIBCOPP_DISABLE_THIS_MT
74  static coroutine_context *gt_current_coroutine = UTIL_CONFIG_NULLPTR;
75 #elif defined(UTIL_CONFIG_THREAD_LOCAL)
76  static UTIL_CONFIG_THREAD_LOCAL coroutine_context *gt_current_coroutine = UTIL_CONFIG_NULLPTR;
77 #else
78  static pthread_once_t gt_coroutine_init_once = PTHREAD_ONCE_INIT;
79  static pthread_key_t gt_coroutine_tls_key;
80  static void init_pthread_this_coroutine_context() { (void)pthread_key_create(&gt_coroutine_tls_key, UTIL_CONFIG_NULLPTR); }
81 #endif
82 
84 #if (defined(LIBCOPP_DISABLE_THIS_MT) && LIBCOPP_DISABLE_THIS_MT) || defined(UTIL_CONFIG_THREAD_LOCAL)
85  gt_current_coroutine = p;
86 #else
87  (void)pthread_once(&gt_coroutine_init_once, init_pthread_this_coroutine_context);
88  pthread_setspecific(gt_coroutine_tls_key, p);
89 #endif
90  }
91 
93 #if (defined(LIBCOPP_DISABLE_THIS_MT) && LIBCOPP_DISABLE_THIS_MT) || defined(UTIL_CONFIG_THREAD_LOCAL)
94  return gt_current_coroutine;
95 #else
96  (void)pthread_once(&gt_coroutine_init_once, init_pthread_this_coroutine_context);
97  return reinterpret_cast<coroutine_context *>(pthread_getspecific(gt_coroutine_tls_key));
98 #endif
99  }
100  } // namespace detail
101 
104 
105  static inline void set_caller(coroutine_context *src, const fcontext::fcontext_t &fctx) {
106  if (UTIL_CONFIG_NULLPTR != src) {
107  src->caller_ = fctx;
108  }
109  }
110 
111  static inline void set_callee(coroutine_context *src, const fcontext::fcontext_t &fctx) {
112  if (UTIL_CONFIG_NULLPTR != src) {
113  src->callee_ = fctx;
114  }
115  }
116 
117 #ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
118  static inline void splitstack_swapcontext(EXPLICIT_UNUSED_ATTR stack_context &from_sctx,
121  if (UTIL_CONFIG_NULLPTR != jump_transfer.from_co) {
122  __splitstack_getcontext(jump_transfer.from_co->callee_stack_.segments_ctx);
123  if (&from_sctx != &jump_transfer.from_co->callee_stack_) {
124  memcpy(&from_sctx.segments_ctx, &jump_transfer.from_co->callee_stack_.segments_ctx, sizeof(from_sctx.segments_ctx));
125  }
126  } else {
127  __splitstack_getcontext(from_sctx.segments_ctx);
128  }
129  __splitstack_setcontext(to_sctx.segments_ctx);
130  }
131 #endif
132 
134  assert(src_ctx.data);
135  if (UTIL_CONFIG_NULLPTR == src_ctx.data) {
136  abort();
137  // return; // clang-analyzer will report "Unreachable code"
138  }
139 
140  // copy jump_src_data_t in case it's destroyed later
141  jump_src_data_t jump_src = *reinterpret_cast<jump_src_data_t *>(src_ctx.data);
142 
143  // this must in a coroutine
144  coroutine_context *ins_ptr = jump_src.to_co;
145  assert(ins_ptr);
146  if (UTIL_CONFIG_NULLPTR == ins_ptr) {
147  abort();
148  // return; // clang-analyzer will report "Unreachable code"
149  }
150 
151  // update caller of to_co
152  ins_ptr->caller_ = src_ctx.fctx;
153 
154  // save from_co's fcontext and switch status
155  if (UTIL_CONFIG_NULLPTR != jump_src.from_co) {
156  jump_src.from_co->callee_ = src_ctx.fctx;
157  }
158 
159  // this_coroutine
161 
162  // run logic code
163  ins_ptr->run_and_recv_retcode(jump_src.priv_data);
164 
166  // add memory fence to flush flags_(used in is_finished())
167  // UTIL_LOCK_ATOMIC_THREAD_FENCE(util::lock::memory_order_release);
168 
169  // jump back to caller
170  ins_ptr->yield();
171  }
172  };
180  static inline void jump_to(fcontext::fcontext_t &to_fctx, EXPLICIT_UNUSED_ATTR stack_context &from_sctx,
182  libcopp_inner_api_helper::jump_src_data_t &jump_transfer) UTIL_CONFIG_NOEXCEPT {
183 
186  // int from_status;
187  // bool swap_success;
188  // can not use any more stack now
189  // can not initialize those vars here
190 
191 #ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
192  assert(&from_sctx != &to_sctx);
193  // ROOT->A: jump_transfer.from_co == UTIL_CONFIG_NULLPTR, jump_transfer.to_co == A, from_sctx == A.caller_stack_, skip backup
194  // segments A->B.start(): jump_transfer.from_co == A, jump_transfer.to_co == B, from_sctx == B.caller_stack_, backup segments
195  // B.yield()->A: jump_transfer.from_co == B, jump_transfer.to_co == UTIL_CONFIG_NULLPTR, from_sctx == B.callee_stack_, skip backup
196  // segments
197  libcopp_inner_api_helper::splitstack_swapcontext(from_sctx, to_sctx, jump_transfer);
198 #endif
199  res = copp::fcontext::copp_jump_fcontext(to_fctx, &jump_transfer);
200  if (UTIL_CONFIG_NULLPTR == res.data) {
201  abort();
202  return;
203  }
204  jump_src = reinterpret_cast<libcopp_inner_api_helper::jump_src_data_t *>(res.data);
205  assert(jump_src);
206 
221  // update caller of to_co if not jump from yield mode
223 
225 
226  // private data
227  jump_transfer.priv_data = jump_src->priv_data;
228 
229  // this_coroutine
230  detail::set_this_coroutine_context(jump_transfer.from_co);
231  }
232 
233  coroutine_context::coroutine_context() UTIL_CONFIG_NOEXCEPT : runner_ret_code_(0),
234  flags_(0),
235  runner_(UTIL_CONFIG_NULLPTR),
236  priv_data_(UTIL_CONFIG_NULLPTR),
237  private_buffer_size_(0),
238  caller_(UTIL_CONFIG_NULLPTR),
239  callee_(UTIL_CONFIG_NULLPTR),
240  callee_stack_(),
241 #ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
242  caller_stack_(),
243 #endif
244  status_(status_t::EN_CRS_INVALID) {
245  }
246 
248 
249  int coroutine_context::create(coroutine_context *p, callback_t &runner, const stack_context &callee_stack, size_t coroutine_size,
250  size_t private_buffer_size) UTIL_CONFIG_NOEXCEPT {
251  if (UTIL_CONFIG_NULLPTR == p) {
252  return COPP_EC_ARGS_ERROR;
253  }
254 
255  // must aligned to sizeof(size_t)
256  if (0 != (private_buffer_size & (sizeof(size_t) - 1))) {
257  return COPP_EC_ARGS_ERROR;
258  }
259 
260  if (0 != (coroutine_size & (sizeof(size_t) - 1))) {
261  return COPP_EC_ARGS_ERROR;
262  }
263 
264  size_t stack_offset = private_buffer_size + coroutine_size;
265  if (UTIL_CONFIG_NULLPTR == callee_stack.sp || callee_stack.size <= stack_offset) {
266  return COPP_EC_ARGS_ERROR;
267  }
268 
269  // stack down
270  // |STACK BUFFER........COROUTINE..this..padding..PRIVATE DATA.....callee_stack.sp|
271  // |------------------------------callee_stack.size -------------------------------|
272  if (callee_stack.sp <= p || coroutine_size < sizeof(coroutine_context)) {
273  return COPP_EC_ARGS_ERROR;
274  }
275 
276  size_t this_offset = reinterpret_cast<unsigned char *>(callee_stack.sp) - reinterpret_cast<unsigned char *>(p);
277  if (this_offset < sizeof(coroutine_context) + private_buffer_size || this_offset > stack_offset) {
278  return COPP_EC_ARGS_ERROR;
279  }
280 
281  // if runner is empty, we can set it later
282  p->set_runner(COPP_MACRO_STD_MOVE(runner));
283 
284  if (&p->callee_stack_ != &callee_stack) {
285  p->callee_stack_ = callee_stack;
286  }
287  p->private_buffer_size_ = private_buffer_size;
288 
289  // stack down, left enough private data
290  p->priv_data_ = reinterpret_cast<unsigned char *>(p->callee_stack_.sp) - p->private_buffer_size_;
291  p->callee_ = fcontext::copp_make_fcontext(reinterpret_cast<unsigned char *>(p->callee_stack_.sp) - stack_offset,
292  p->callee_stack_.size - stack_offset, &libcopp_inner_api_helper::coroutine_context_callback);
293  if (UTIL_CONFIG_NULLPTR == p->callee_) {
295  }
296 
297  return COPP_EC_SUCCESS;
298  }
299 
300  int coroutine_context::start(void *priv_data) {
301  if (UTIL_CONFIG_NULLPTR == callee_) {
302  return COPP_EC_NOT_INITED;
303  }
304 
305  int from_status = status_t::EN_CRS_READY;
306  do {
307  if (from_status < status_t::EN_CRS_READY) {
308  return COPP_EC_NOT_INITED;
309  }
310 
311  if (status_.compare_exchange_strong(from_status, status_t::EN_CRS_RUNNING, util::lock::memory_order_acq_rel,
313  break;
314  } else {
315  // finished or stoped
316  if (from_status > status_t::EN_CRS_RUNNING) {
317  return COPP_EC_NOT_READY;
318  }
319 
320  // already running
321  if (status_t::EN_CRS_RUNNING == from_status) {
322  return COPP_EC_IS_RUNNING;
323  }
324  }
325  } while (true);
326 
327  jump_src_data_t jump_data;
329  jump_data.to_co = this;
330  jump_data.priv_data = priv_data;
331 
332 #ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
333  jump_to(callee_, caller_stack_, callee_stack_, jump_data);
334 #else
335  jump_to(callee_, callee_stack_, callee_stack_, jump_data);
336 #endif
337 
338  // Move changing status to EN_CRS_EXITED is finished
339  if (check_flags(flag_t::EN_CFT_FINISHED)) {
340  // if in finished status, change it to exited
341  status_.store(status_t::EN_CRS_EXITED, util::lock::memory_order_release);
342  }
343 
344  return COPP_EC_SUCCESS;
345  } // namespace copp
346 
347  int coroutine_context::resume(void *priv_data) { return start(priv_data); }
348 
349  int coroutine_context::yield(void **priv_data) {
350  if (UTIL_CONFIG_NULLPTR == callee_) {
351  return COPP_EC_NOT_INITED;
352  }
353 
354  int from_status = status_t::EN_CRS_RUNNING;
355  int to_status = status_t::EN_CRS_READY;
356  if (check_flags(flag_t::EN_CFT_FINISHED)) {
357  to_status = status_t::EN_CRS_FINISHED;
358  }
359  if (false ==
360  status_.compare_exchange_strong(from_status, to_status, util::lock::memory_order_acq_rel, util::lock::memory_order_acquire)) {
361  switch (from_status) {
362  case status_t::EN_CRS_INVALID:
363  return COPP_EC_NOT_INITED;
364  case status_t::EN_CRS_READY:
365  return COPP_EC_NOT_RUNNING;
366  case status_t::EN_CRS_FINISHED:
367  case status_t::EN_CRS_EXITED:
368  return COPP_EC_ALREADY_EXIST;
369  default:
370  return COPP_EC_UNKNOWN;
371  }
372  }
373 
374  // success or finished will continue
375  jump_src_data_t jump_data;
376  jump_data.from_co = this;
377  jump_data.to_co = UTIL_CONFIG_NULLPTR;
378 
379 
380 #ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
381  jump_to(caller_, callee_stack_, caller_stack_, jump_data);
382 #else
383  jump_to(caller_, callee_stack_, callee_stack_, jump_data);
384 #endif
385 
386  if (UTIL_CONFIG_NULLPTR != priv_data) {
387  *priv_data = jump_data.priv_data;
388  }
389 
390  return COPP_EC_SUCCESS;
391  }
392 
394  if (flags & flag_t::EN_CFT_MASK) {
395  return false;
396  }
397 
398  flags_ |= flags;
399  return true;
400  }
401 
403  if (flags & flag_t::EN_CFT_MASK) {
404  return false;
405  }
406 
407  flags_ &= ~flags;
408  return true;
409  }
410 
411  bool coroutine_context::check_flags(int flags) const { return 0 != (flags_ & flags); }
412 
413 #if defined(UTIL_CONFIG_COMPILER_CXX_RVALUE_REFERENCES) && UTIL_CONFIG_COMPILER_CXX_RVALUE_REFERENCES
415 #else
417 #endif
418  if (!runner) {
419  return COPP_EC_ARGS_ERROR;
420  }
421 
422  int from_status = status_t::EN_CRS_INVALID;
423  if (false == status_.compare_exchange_strong(from_status, status_t::EN_CRS_READY, util::lock::memory_order_acq_rel,
425  return COPP_EC_ALREADY_INITED;
426  }
427 
428  runner_ = COPP_MACRO_STD_MOVE(runner);
429  return COPP_EC_SUCCESS;
430  }
431 
432  bool coroutine_context::is_finished() const UTIL_CONFIG_NOEXCEPT {
433  // return !!(flags_ & flag_t::EN_CFT_FINISHED);
434  return status_.load(util::lock::memory_order_acquire) >= status_t::EN_CRS_FINISHED;
435  }
436 
437  namespace this_coroutine {
439 
440  int yield(void **priv_data) {
442  if (UTIL_CONFIG_NULLPTR != pco) {
443  return pco->yield(priv_data);
444  }
445 
446  return COPP_EC_NOT_RUNNING;
447  }
448  } // namespace this_coroutine
449 } // namespace copp
static coroutine_context * get_this_coroutine_context()
static void set_callee(coroutine_context *src, const fcontext::fcontext_t &fctx)
std::function< int(void *)> callback_t
COPP_EC_ALREADY_EXIST.
Definition: errno.h:29
static LIBCOPP_SYMBOL_LOCAL void coroutine_context_callback(::copp::fcontext::transfer_t src_ctx)
static int create(coroutine_context *p, callback_t &runner, const stack_context &callee_stack, size_t coroutine_size, size_t private_buffer_size) UTIL_CONFIG_NOEXCEPT
create coroutine context at stack context callee_
导入继承关系约束 Licensed under the MIT licenses.
COPP_EC_ALREADY_INITED.
Definition: errno.h:22
COPP_EC_NOT_INITED.
Definition: errno.h:21
COPP_EC_FCONTEXT_MAKE_FAILED.
Definition: errno.h:33
COPP_EC_NOT_RUNNING.
Definition: errno.h:25
static void init_pthread_this_coroutine_context()
COPP_EC_NOT_READY.
Definition: errno.h:24
COPP_EC_SUCCESS.
Definition: errno.h:12
int set_runner(const callback_t &runner)
set runner
void run_and_recv_retcode(void *priv_data)
coroutine entrance function
COPP_BOOST_CONTEXT_DECL fcontext_t COPP_BOOST_CONTEXT_CALLDECL copp_make_fcontext(void *sp, std::size_t size, void(*fn)(transfer_t))
static pthread_key_t gt_coroutine_tls_key
int start(void *priv_data=UTIL_CONFIG_NULLPTR)
start coroutine
coroutine_context::jump_src_data_t jump_src_data_t
coroutine_context() UTIL_CONFIG_NOEXCEPT
coroutine_context * get_coroutine() UTIL_CONFIG_NOEXCEPT
get current coroutine
#define EXPLICIT_UNUSED_ATTR
maybe_unused, 标记忽略unused警告 usage: EXPLICIT_UNUSED_ATTR int a; class EXPLICIT_UNUSED_ATTR a; EXP...
COPP_EC_IS_RUNNING.
Definition: errno.h:26
bool is_finished() const UTIL_CONFIG_NOEXCEPT
get runner return code
static void jump_to(fcontext::fcontext_t &to_fctx, EXPLICIT_UNUSED_ATTR stack_context &from_sctx, EXPLICIT_UNUSED_ATTR stack_context &to_sctx, libcopp_inner_api_helper::jump_src_data_t &jump_transfer) UTIL_CONFIG_NOEXCEPT
call platform jump to asm instruction
#define COPP_MACRO_STD_MOVE(x)
Definition: features.h:171
static void set_this_coroutine_context(coroutine_context *p)
int yield(void **priv_data=UTIL_CONFIG_NULLPTR)
yield current coroutine
static pthread_once_t gt_coroutine_init_once
COPP_BOOST_CONTEXT_DECL transfer_t COPP_BOOST_CONTEXT_CALLDECL copp_jump_fcontext(fcontext_t const to, void *vp)
fcontext::fcontext_t callee_
int resume(void *priv_data=UTIL_CONFIG_NULLPTR)
resume coroutine
bool unset_flags(int flags)
set all flags to false
static void set_caller(coroutine_context *src, const fcontext::fcontext_t &fctx)
int yield(void **priv_data=UTIL_CONFIG_NULLPTR)
yield coroutine
#define LIBCOPP_SYMBOL_LOCAL
COPP_EC_UNKNOWN.
Definition: errno.h:14
bool check_flags(int flags) const
check flags
base type of all coroutine context
bool set_flags(int flags)
set all flags to true
fcontext::fcontext_t caller_
COPP_EC_ARGS_ERROR.
Definition: errno.h:30