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