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