libcopp  1.2.0
coroutine_context.cpp
Go to the documentation of this file.
1 #include <algorithm>
2 #include <assert.h>
3 #include <cstdlib>
4 #include <cstring>
5 
7 #include <libcopp/utils/errno.h>
9 
11 
12 #if !(defined(THREAD_TLS_USE_PTHREAD) && THREAD_TLS_USE_PTHREAD) && defined(UTIL_CONFIG_THREAD_LOCAL)
13 // using thread_local
14 #else
15 #include <pthread.h>
16 #endif
17 
18 #ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
19 extern "C" {
20 void __splitstack_getcontext(void * [COPP_MACRO_SEGMENTED_STACK_NUMBER]);
21 
22 void __splitstack_setcontext(void * [COPP_MACRO_SEGMENTED_STACK_NUMBER]);
23 
24 void __splitstack_releasecontext(void * [COPP_MACRO_SEGMENTED_STACK_NUMBER]);
25 
26 void __splitstack_block_signals_context(void * [COPP_MACRO_SEGMENTED_STACK_NUMBER], int *, int *);
27 }
28 #endif
29 
30 namespace copp {
31  namespace detail {
32 
33 #if !(defined(THREAD_TLS_USE_PTHREAD) && THREAD_TLS_USE_PTHREAD) && defined(UTIL_CONFIG_THREAD_LOCAL)
34  static UTIL_CONFIG_THREAD_LOCAL coroutine_context *gt_current_coroutine = UTIL_CONFIG_NULLPTR;
35 #else
36  static pthread_once_t gt_coroutine_init_once = PTHREAD_ONCE_INIT;
37  static pthread_key_t gt_coroutine_tls_key;
38  static void init_pthread_this_coroutine_context() { (void)pthread_key_create(&gt_coroutine_tls_key, UTIL_CONFIG_NULLPTR); }
39 #endif
40 
42 #if !(defined(THREAD_TLS_USE_PTHREAD) && THREAD_TLS_USE_PTHREAD) && defined(UTIL_CONFIG_THREAD_LOCAL)
43  gt_current_coroutine = p;
44 #else
45  (void)pthread_once(&gt_coroutine_init_once, init_pthread_this_coroutine_context);
46  pthread_setspecific(gt_coroutine_tls_key, p);
47 #endif
48  }
49 
51 #if !(defined(THREAD_TLS_USE_PTHREAD) && THREAD_TLS_USE_PTHREAD) && defined(UTIL_CONFIG_THREAD_LOCAL)
52  return gt_current_coroutine;
53 #else
54  (void)pthread_once(&gt_coroutine_init_once, init_pthread_this_coroutine_context);
55  return reinterpret_cast<coroutine_context *>(pthread_getspecific(gt_coroutine_tls_key));
56 #endif
57  }
58  } // namespace detail
59 
60  coroutine_context::coroutine_context() UTIL_CONFIG_NOEXCEPT : runner_ret_code_(0),
61  flags_(0),
62  runner_(UTIL_CONFIG_NULLPTR),
63  priv_data_(UTIL_CONFIG_NULLPTR),
64  private_buffer_size_(0),
65  caller_(UTIL_CONFIG_NULLPTR),
66  callee_(UTIL_CONFIG_NULLPTR),
67  callee_stack_(),
68 #ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
69  caller_stack_(),
70 #endif
71  status_(status_t::EN_CRS_INVALID) {
72  }
73 
75 
76  int coroutine_context::create(coroutine_context *p, callback_t &runner, const stack_context &callee_stack, size_t coroutine_size,
77  size_t private_buffer_size) UTIL_CONFIG_NOEXCEPT {
78  if (UTIL_CONFIG_NULLPTR == p) {
79  return COPP_EC_ARGS_ERROR;
80  }
81 
82  // must aligned to sizeof(size_t)
83  if (0 != (private_buffer_size & (sizeof(size_t) - 1))) {
84  return COPP_EC_ARGS_ERROR;
85  }
86 
87  if (0 != (coroutine_size & (sizeof(size_t) - 1))) {
88  return COPP_EC_ARGS_ERROR;
89  }
90 
91  size_t stack_offset = private_buffer_size + coroutine_size;
92  if (NULL == callee_stack.sp || callee_stack.size <= stack_offset) {
93  return COPP_EC_ARGS_ERROR;
94  }
95 
96  // stack down
97  // |STARCK BUFFER........COROUTINE..this..padding..PRIVATE DATA.....callee_stack.sp|
98  // |------------------------------callee_stack.size -------------------------------|
99  if (callee_stack.sp <= p || coroutine_size < sizeof(coroutine_context)) {
100  return COPP_EC_ARGS_ERROR;
101  }
102 
103  size_t this_offset = reinterpret_cast<unsigned char *>(callee_stack.sp) - reinterpret_cast<unsigned char *>(p);
104  if (this_offset < sizeof(coroutine_context) + private_buffer_size || this_offset > stack_offset) {
105  return COPP_EC_ARGS_ERROR;
106  }
107 
108  // if runner is empty, we can set it later
109  p->set_runner(COPP_MACRO_STD_MOVE(runner));
110 
111  if (&p->callee_stack_ != &callee_stack) {
112  p->callee_stack_ = callee_stack;
113  }
114  p->private_buffer_size_ = private_buffer_size;
115 
116  // stack down, left enough private data
117  p->priv_data_ = reinterpret_cast<unsigned char *>(p->callee_stack_.sp) - p->private_buffer_size_;
118  p->callee_ = fcontext::copp_make_fcontext(reinterpret_cast<unsigned char *>(p->callee_stack_.sp) - stack_offset,
119  p->callee_stack_.size - stack_offset, &coroutine_context::coroutine_context_callback);
120  if (NULL == p->callee_) {
122  }
123 
124  return COPP_EC_SUCCESS;
125  }
126 
127  int coroutine_context::start(void *priv_data) {
128  if (NULL == callee_) {
129  return COPP_EC_NOT_INITED;
130  }
131 
132  int from_status = status_t::EN_CRS_READY;
133  do {
134  if (from_status < status_t::EN_CRS_READY) {
135  return COPP_EC_NOT_INITED;
136  }
137 
138  if (status_.compare_exchange_strong(from_status, status_t::EN_CRS_RUNNING, util::lock::memory_order_acq_rel,
140  break;
141  } else {
142  // finished or stoped
143  if (from_status > status_t::EN_CRS_RUNNING) {
144  return COPP_EC_NOT_READY;
145  }
146 
147  // already running
148  if (status_t::EN_CRS_RUNNING == from_status) {
149  return COPP_EC_IS_RUNNING;
150  }
151  }
152  } while (true);
153 
154  jump_src_data_t jump_data;
156  jump_data.to_co = this;
157  jump_data.priv_data = priv_data;
158 
159 #ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
160  jump_to(callee_, caller_stack_, callee_stack_, jump_data);
161 #else
162  jump_to(callee_, callee_stack_, callee_stack_, jump_data);
163 #endif
164 
165  // [BUG #4](https://github.com/owt5008137/libcopp/issues/4)
166  // Move changing status to the end of start(private data)
167  {
168  // assume it's running, or set into EN_CRS_EXITED if in EN_CRS_FINISHED
169  from_status = status_t::EN_CRS_RUNNING;
170  if (false == status_.compare_exchange_strong(from_status, status_t::EN_CRS_READY, util::lock::memory_order_acq_rel,
172  if (status_t::EN_CRS_FINISHED == from_status) {
173  // if in finished status, change it to exited
174  status_.store(status_t::EN_CRS_EXITED, util::lock::memory_order_release);
175  }
176  }
177  }
178 
179  return COPP_EC_SUCCESS;
180  }
181 
182  int coroutine_context::resume(void *priv_data) { return start(priv_data); }
183 
184  int coroutine_context::yield(void **priv_data) {
185  if (UTIL_CONFIG_NULLPTR == callee_) {
186  return COPP_EC_NOT_INITED;
187  }
188 
189  int from_status = status_t::EN_CRS_RUNNING;
190  if (false == status_.compare_exchange_strong(from_status, status_t::EN_CRS_READY, util::lock::memory_order_acq_rel,
192  switch (from_status) {
193  case status_t::EN_CRS_INVALID:
194  return COPP_EC_NOT_INITED;
195  case status_t::EN_CRS_READY:
196  return COPP_EC_NOT_RUNNING;
197  case status_t::EN_CRS_FINISHED:
198  break;
199  case status_t::EN_CRS_EXITED:
200  return COPP_EC_ALREADY_EXIST;
201  default:
202  return COPP_EC_UNKNOWN;
203  }
204  }
205 
206  // success or finished will continue
207  jump_src_data_t jump_data;
208  jump_data.from_co = this;
209  jump_data.to_co = UTIL_CONFIG_NULLPTR;
210 
211 
212 #ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
213  jump_to(caller_, callee_stack_, caller_stack_, jump_data);
214 #else
215  jump_to(caller_, callee_stack_, callee_stack_, jump_data);
216 #endif
217 
218  if (UTIL_CONFIG_NULLPTR != priv_data) {
219  *priv_data = jump_data.priv_data;
220  }
221 
222  return COPP_EC_SUCCESS;
223  }
224 
226  if (flags & flag_t::EN_CFT_MASK) {
227  return false;
228  }
229 
230  flags_ |= flags;
231  return true;
232  }
233 
235  if (flags & flag_t::EN_CFT_MASK) {
236  return false;
237  }
238 
239  flags_ &= ~flags;
240  return true;
241  }
242 
243  bool coroutine_context::check_flags(int flags) const { return 0 != (flags_ & flags); }
244 
245 #if defined(UTIL_CONFIG_COMPILER_CXX_RVALUE_REFERENCES) && UTIL_CONFIG_COMPILER_CXX_RVALUE_REFERENCES
247 #else
249 #endif
250  if (!runner) {
251  return COPP_EC_ARGS_ERROR;
252  }
253 
254  int from_status = status_t::EN_CRS_INVALID;
255  if (false == status_.compare_exchange_strong(from_status, status_t::EN_CRS_READY, util::lock::memory_order_acq_rel,
257  return COPP_EC_ALREADY_INITED;
258  }
259 
260  runner_ = COPP_MACRO_STD_MOVE(runner);
261  return COPP_EC_SUCCESS;
262  }
263 
264  bool coroutine_context::is_finished() const UTIL_CONFIG_NOEXCEPT {
265  // return !!(flags_ & flag_t::EN_CFT_FINISHED);
266  return status_.load(util::lock::memory_order_acquire) >= status_t::EN_CRS_FINISHED;
267  }
268 
270  EXPLICIT_UNUSED_ATTR stack_context &to_sctx, jump_src_data_t &jump_transfer) UTIL_CONFIG_NOEXCEPT {
271 
273  jump_src_data_t * jump_src;
274  // int from_status;
275  // bool swap_success;
276  // can not use any more stack now
277  // can not initialize those vars here
278 
279 #ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
280  assert(&from_sctx != &to_sctx);
281  // ROOT->A: jump_transfer.from_co == NULL, jump_transfer.to_co == A, from_sctx == A.caller_stack_, skip backup segments
282  // A->B.start(): jump_transfer.from_co == A, jump_transfer.to_co == B, from_sctx == B.caller_stack_, backup segments
283  // B.yield()->A: jump_transfer.from_co == B, jump_transfer.to_co == NULL, from_sctx == B.callee_stack_, skip backup segments
284  if (UTIL_CONFIG_NULLPTR != jump_transfer.from_co) {
285  __splitstack_getcontext(jump_transfer.from_co->callee_stack_.segments_ctx);
286  if (&from_sctx != &jump_transfer.from_co->callee_stack_) {
287  memcpy(&from_sctx.segments_ctx, &jump_transfer.from_co->callee_stack_.segments_ctx, sizeof(from_sctx.segments_ctx));
288  }
289  } else {
290  __splitstack_getcontext(from_sctx.segments_ctx);
291  }
292  __splitstack_setcontext(to_sctx.segments_ctx);
293 #endif
294  res = copp::fcontext::copp_jump_fcontext(to_fctx, &jump_transfer);
295  if (NULL == res.data) {
296  abort();
297  return;
298  }
299  jump_src = reinterpret_cast<jump_src_data_t *>(res.data);
300  assert(jump_src);
301 
316  // update caller of to_co if not jump from yield mode
317  if (UTIL_CONFIG_NULLPTR != jump_src->to_co) {
318  jump_src->to_co->caller_ = res.fctx;
319  }
320 
321  if (UTIL_CONFIG_NULLPTR != jump_src->from_co) {
322  jump_src->from_co->callee_ = res.fctx;
323  // [BUG #4](https://github.com/owt5008137/libcopp/issues/4)
324  // from_status = jump_src->from_co->status_.load();
325  // if (status_t::EN_CRS_RUNNING == from_status) {
326  // jump_src->from_co->status_.compare_exchange_strong(from_status, status_t::EN_CRS_READY, util::lock::memory_order_acq_rel,
327  // util::lock::memory_order_acquire);
328  // } else if (status_t::EN_CRS_FINISHED == from_status) {
329  // // if in finished status, change it to exited
330  // jump_src->from_co->status_.store(status_t::EN_CRS_EXITED);
331  // }
332  }
333 
334  // private data
335  jump_transfer.priv_data = jump_src->priv_data;
336 
337  // this_coroutine
338  detail::set_this_coroutine_context(jump_transfer.from_co);
339 
340  // [BUG #4](https://github.com/owt5008137/libcopp/issues/4)
341  // // resume running status of from_co
342  // if (NULL != jump_transfer.from_co) {
343  // from_status = jump_transfer.from_co->status_.load();
344  // swap_success = false;
345  // while (!swap_success && status_t::EN_CRS_READY == from_status) {
346  // swap_success = jump_transfer.from_co->status_.compare_exchange_strong(from_status, status_t::EN_CRS_RUNNING,
347  // util::lock::memory_order_acq_rel, util::lock::memory_order_acquire);
348  // }
349  // }
350  }
351 
353  assert(src_ctx.data);
354  if (NULL == src_ctx.data) {
355  abort();
356  // return; // clang-analyzer will report "Unreachable code"
357  }
358 
359  // copy jump_src_data_t in case it's destroyed later
360  jump_src_data_t jump_src = *reinterpret_cast<jump_src_data_t *>(src_ctx.data);
361 
362  // this must in a coroutine
363  coroutine_context *ins_ptr = jump_src.to_co;
364  assert(ins_ptr);
365  if (NULL == ins_ptr) {
366  abort();
367  // return; // clang-analyzer will report "Unreachable code"
368  }
369 
370  // update caller of to_co
371  ins_ptr->caller_ = src_ctx.fctx;
372 
373  // save from_co's fcontext and switch status
374  if (UTIL_CONFIG_NULLPTR != jump_src.from_co) {
375  jump_src.from_co->callee_ = src_ctx.fctx;
376  // [BUG #4](https://github.com/owt5008137/libcopp/issues/4)
377  // int from_status = status_t::EN_CRS_RUNNING; // from coroutine change status from running to ready
378  // jump_src.from_co->status_.compare_exchange_strong(from_status, status_t::EN_CRS_READY, util::lock::memory_order_acq_rel,
379  // util::lock::memory_order_acquire);
380  }
381 
382  // this_coroutine
384 
385  // run logic code
386  ins_ptr->run_and_recv_retcode(jump_src.priv_data);
387 
388  ins_ptr->flags_ |= flag_t::EN_CFT_FINISHED;
389  ins_ptr->status_.store(status_t::EN_CRS_FINISHED, util::lock::memory_order_release);
390  // add memory fence to flush flags_(used in is_finished())
391  // UTIL_LOCK_ATOMIC_THREAD_FENCE(util::lock::memory_order_release);
392 
393  // jump back to caller
394  ins_ptr->yield();
395  }
396 
397  namespace this_coroutine {
399 
400  int yield(void **priv_data) {
402  if (UTIL_CONFIG_NULLPTR != pco) {
403  return pco->yield(priv_data);
404  }
405 
406  return COPP_EC_NOT_RUNNING;
407  }
408  } // namespace this_coroutine
409 } // namespace copp
static coroutine_context * get_this_coroutine_context()
std::function< int(void *)> callback_t
COPP_EC_ALREADY_EXIST.
Definition: errno.h:29
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.
void store(value_type desired, EXPLICIT_UNUSED_ATTR::util::lock::memory_order order=::util::lock::memory_order_seq_cst) UTIL_CONFIG_NOEXCEPT
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() 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, stack_context &from_sctx, stack_context &to_sctx, jump_src_data_t &jump_transfer) UTIL_CONFIG_NOEXCEPT
call platform jump to asm instruction
#define COPP_MACRO_STD_MOVE(x)
Definition: features.h:185
util::lock::atomic_int_type< int > status_
static void set_this_coroutine_context(coroutine_context *p)
static void coroutine_context_callback(::copp::fcontext::transfer_t src_ctx)
fcontext entrance function
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
int yield(void **priv_data=UTIL_CONFIG_NULLPTR)
yield coroutine
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