libcopp 2.3.1
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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
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(LIBCOPP_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
27extern "C" {
28void __splitstack_getcontext(void *[LIBCOPP_MACRO_SEGMENTED_STACK_NUMBER]);
29
30void __splitstack_setcontext(void *[LIBCOPP_MACRO_SEGMENTED_STACK_NUMBER]);
31
32void __splitstack_releasecontext(void *[LIBCOPP_MACRO_SEGMENTED_STACK_NUMBER]);
33
34void __splitstack_block_signals_context(void *[LIBCOPP_MACRO_SEGMENTED_STACK_NUMBER], int *, int *);
35}
36#endif
37
38LIBCOPP_COPP_NAMESPACE_BEGIN
39namespace detail {
40
41#if !LIBCOPP_MACRO_ENABLE_MULTI_THREAD
43#elif defined(LIBCOPP_MACRO_THREAD_LOCAL)
44static LIBCOPP_MACRO_THREAD_LOCAL coroutine_context_base *gt_current_coroutine = nullptr;
45#else
46static pthread_once_t gt_coroutine_init_once = PTHREAD_ONCE_INIT;
47static pthread_key_t gt_coroutine_tls_key;
48static void init_pthread_this_coroutine_context() { (void)pthread_key_create(&gt_coroutine_tls_key, nullptr); }
49#endif
50
52#if !LIBCOPP_MACRO_ENABLE_MULTI_THREAD || defined(LIBCOPP_MACRO_THREAD_LOCAL)
54#else
55 (void)pthread_once(&gt_coroutine_init_once, init_pthread_this_coroutine_context);
56 pthread_setspecific(gt_coroutine_tls_key, p);
57#endif
58}
59
61#if !LIBCOPP_MACRO_ENABLE_MULTI_THREAD || defined(LIBCOPP_MACRO_THREAD_LOCAL)
63#else
64 (void)pthread_once(&gt_coroutine_init_once, init_pthread_this_coroutine_context);
65 return reinterpret_cast<coroutine_context_base *>(pthread_getspecific(gt_coroutine_tls_key));
66#endif
67}
68} // namespace detail
69
70LIBCOPP_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
80LIBCOPP_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
89LIBCOPP_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
98LIBCOPP_COPP_API bool coroutine_context_base::check_flags(int flags) const LIBCOPP_MACRO_NOEXCEPT {
99 return 0 != (flags_ & flags);
100}
101
102LIBCOPP_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,
109 LIBCOPP_COPP_NAMESPACE_ID::util::lock::memory_order_acq_rel,
110 LIBCOPP_COPP_NAMESPACE_ID::util::lock::memory_order_acquire)) {
112 }
113
114 runner_ = std::move(runner);
115 return COPP_EC_SUCCESS;
116}
117
118LIBCOPP_COPP_API bool coroutine_context_base::is_finished() const LIBCOPP_MACRO_NOEXCEPT {
119 // return !!(flags_ & flag_type::EN_CFT_FINISHED);
120 return status_.load(LIBCOPP_COPP_NAMESPACE_ID::util::lock::memory_order_acquire) >= status_type::EN_CRS_FINISHED;
121}
122
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 LIBCOPP_UTIL_FORCEINLINE static void splitstack_swapcontext(
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};
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
263
264 // private data
265 jump_transfer.priv_data = jump_src->priv_data;
266
267 // this_coroutine
269}
270
271LIBCOPP_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 =
313 static_cast<size_t>(reinterpret_cast<unsigned char *>(callee_stack.sp) - reinterpret_cast<unsigned char *>(p));
314 if (this_offset < sizeof(coroutine_context) + private_buffer_size || this_offset > stack_offset) {
315 return COPP_EC_ARGS_ERROR;
316 }
317
318 // if runner is empty, we can set it later
319 p->set_runner(std::move(runner));
320
321 if (&p->callee_stack_ != &callee_stack) {
322 p->callee_stack_ = callee_stack;
323 }
324 p->private_buffer_size_ = private_buffer_size;
325
326 // stack down, left enough private data
327 p->priv_data_ = reinterpret_cast<unsigned char *>(p->callee_stack_.sp) - p->private_buffer_size_;
328 p->callee_ = fcontext::copp_make_fcontext_v2(reinterpret_cast<unsigned char *>(p->callee_stack_.sp) - stack_offset,
329 p->callee_stack_.size - stack_offset,
331 if (nullptr == p->callee_) {
333 }
334
335 return COPP_EC_SUCCESS;
336}
337
338#if defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
339LIBCOPP_COPP_API int coroutine_context::start(void *priv_data) {
340 std::exception_ptr eptr;
341 int ret = start(eptr, priv_data);
342 maybe_rethrow(eptr);
343 return ret;
344}
345
346LIBCOPP_COPP_API int coroutine_context::start(std::exception_ptr &unhandled, void *priv_data) LIBCOPP_MACRO_NOEXCEPT {
347#else
348LIBCOPP_COPP_API int coroutine_context::start(void *priv_data) {
349#endif
350 if (nullptr == callee_) {
351 return COPP_EC_NOT_INITED;
352 }
353
354#if defined(LIBCOPP_MACRO_ENABLE_WIN_FIBER) && LIBCOPP_MACRO_ENABLE_WIN_FIBER
355 {
357 if (this_ctx && this_ctx->check_flags(flag_type::EN_CFT_IS_FIBER)) {
358 return LIBCOPP_COPP_NAMESPACE_ID::COPP_EC_CAN_NOT_USE_CROSS_FCONTEXT_AND_FIBER;
359 }
360 }
361#endif
362
363 int from_status = status_type::EN_CRS_READY;
364 do {
365 if (from_status < status_type::EN_CRS_READY) {
366 return COPP_EC_NOT_INITED;
367 }
368
369 if (status_.compare_exchange_strong(from_status, status_type::EN_CRS_RUNNING,
370 LIBCOPP_COPP_NAMESPACE_ID::util::lock::memory_order_acq_rel,
371 LIBCOPP_COPP_NAMESPACE_ID::util::lock::memory_order_acquire)) {
372 break;
373 } else {
374 // finished or stoped
375 if (from_status > status_type::EN_CRS_RUNNING) {
376 return COPP_EC_NOT_READY;
377 }
378
379 // already running
380 if (status_type::EN_CRS_RUNNING == from_status) {
381 return COPP_EC_IS_RUNNING;
382 }
383 }
384 } while (true);
385
386 jump_src_data_t jump_data;
387#if defined(LIBCOPP_MACRO_ENABLE_WIN_FIBER) && LIBCOPP_MACRO_ENABLE_WIN_FIBER
388 jump_data.from_co = LIBCOPP_COPP_NAMESPACE_ID::this_coroutine::get_coroutine();
389#else
391#endif
392 jump_data.to_co = this;
393 jump_data.priv_data = priv_data;
394
395#ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
396 jump_to(callee_, caller_stack_, callee_stack_, jump_data);
397#else
399#endif
400
401 // Move changing status to EN_CRS_EXITED is finished
403 // if in finished status, change it to exited
404 status_.store(status_type::EN_CRS_EXITED, LIBCOPP_COPP_NAMESPACE_ID::util::lock::memory_order_release);
405 }
406
407#if defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
408 if LIBCOPP_UTIL_UNLIKELY_CONDITION (unhandle_exception_) {
409 std::swap(unhandled, unhandle_exception_);
410 }
411#endif
412
413 return COPP_EC_SUCCESS;
414}
415
416LIBCOPP_COPP_API int coroutine_context::resume(void *priv_data) { return start(priv_data); }
417#if defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
418LIBCOPP_COPP_API int coroutine_context::resume(std::exception_ptr &unhandled, void *priv_data) LIBCOPP_MACRO_NOEXCEPT {
419 return start(unhandled, priv_data);
420}
421#endif
422
423LIBCOPP_COPP_API int coroutine_context::yield(void **priv_data) LIBCOPP_MACRO_NOEXCEPT {
424 if (nullptr == callee_) {
425 return COPP_EC_NOT_INITED;
426 }
427
428 int from_status = status_type::EN_CRS_RUNNING;
429 int to_status = status_type::EN_CRS_READY;
430 if (check_flags(flag_type::EN_CFT_FINISHED)) {
431 to_status = status_type::EN_CRS_FINISHED;
432 }
433 if (false == status_.compare_exchange_strong(from_status, to_status,
434 LIBCOPP_COPP_NAMESPACE_ID::util::lock::memory_order_acq_rel,
435 LIBCOPP_COPP_NAMESPACE_ID::util::lock::memory_order_acquire)) {
436 switch (from_status) {
437 case status_type::EN_CRS_INVALID:
438 return COPP_EC_NOT_INITED;
439 case status_type::EN_CRS_READY:
440 return COPP_EC_NOT_RUNNING;
441 case status_type::EN_CRS_FINISHED:
442 case status_type::EN_CRS_EXITED:
444 default:
445 return COPP_EC_UNKNOWN;
446 }
447 }
448
449 // success or finished will continue
450 jump_src_data_t jump_data;
451 jump_data.from_co = this;
452 jump_data.to_co = nullptr;
453
454#ifdef LIBCOPP_MACRO_USE_SEGMENTED_STACKS
455 jump_to(caller_, callee_stack_, caller_stack_, jump_data);
456#else
457 jump_to(caller_, callee_stack_, callee_stack_, jump_data);
458#endif
459
460 if (nullptr != priv_data) {
461 *priv_data = jump_data.priv_data;
462 }
463
464 return COPP_EC_SUCCESS;
465}
466
467namespace this_coroutine {
468LIBCOPP_COPP_API coroutine_context *get_coroutine() LIBCOPP_MACRO_NOEXCEPT {
470#if defined(LIBCOPP_MACRO_ENABLE_WIN_FIBER) && LIBCOPP_MACRO_ENABLE_WIN_FIBER
472 ret = nullptr;
473 }
474#endif
475 return static_cast<coroutine_context *>(ret);
476}
477
478LIBCOPP_COPP_API int yield(void **priv_data) LIBCOPP_MACRO_NOEXCEPT {
479#if defined(LIBCOPP_MACRO_ENABLE_WIN_FIBER) && LIBCOPP_MACRO_ENABLE_WIN_FIBER
481#else
483#endif
484 if LIBCOPP_UTIL_LIKELY_CONDITION (nullptr != pco) {
485 return pco->yield(priv_data);
486 }
487
488 return COPP_EC_NOT_RUNNING;
489}
490} // namespace this_coroutine
491LIBCOPP_COPP_NAMESPACE_END
base type of all coroutine context
LIBCOPP_COPP_API ~coroutine_context_base()
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
LIBCOPP_COPP_NAMESPACE_ID::util::lock::atomic_int_type< LIBCOPP_COPP_NAMESPACE_ID::util::lock::unsafe_int_type< int > > status_
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_UTIL_FORCEINLINE void run_and_recv_retcode(void *priv_data)
coroutine entrance function
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_NAMESPACE_ID::util::lock::atomic_int_type< LIBCOPP_COPP_NAMESPACE_ID::util::lock::unsafe_int_type< int > > status_
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
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 LIBCOPP_UTIL_FORCEINLINE
#define LIBCOPP_UTIL_LIKELY_CONDITION(__C)
#define LIBCOPP_UTIL_UNLIKELY_CONDITION(__C)
static void jump_to(fcontext::fcontext_t &to_fctx, LIBCOPP_EXPLICIT_UNUSED_ATTR stack_context &from_sctx, LIBCOPP_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_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:35
@ 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
#define LIBCOPP_EXPLICIT_UNUSED_ATTR
maybe_unused attribute usage: LIBCOPP_EXPLICIT_UNUSED_ATTR int a; class LIBCOPP_EXPLICIT_UNUSED_ATTR ...
static coroutine_context_base * gt_current_coroutine
static void set_this_coroutine_context(coroutine_context_base *p)
static coroutine_context_base * get_this_coroutine_context()
LIBCOPP_BOOST_CONTEXT_DECL fcontext_t LIBCOPP_BOOST_CONTEXT_CALLDECL copp_make_fcontext_v2(void *sp, std::size_t size, void(*fn)(transfer_t))
LIBCOPP_COPP_API_HEAD_ONLY void swap(LIBCOPP_COPP_NAMESPACE_ID::memory::strong_rc_ptr< T > &a, LIBCOPP_COPP_NAMESPACE_ID::memory::strong_rc_ptr< T > &b) noexcept
Support std::swap for strong_rc_ptr.
Definition rc_ptr.h:1377
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
status of safe coroutine context base
static void coroutine_context_callback(LIBCOPP_COPP_NAMESPACE_ID::fcontext::transfer_t src_ctx)
static LIBCOPP_UTIL_FORCEINLINE void set_callee(coroutine_context *src, const fcontext::fcontext_t &fctx)
static LIBCOPP_UTIL_FORCEINLINE void set_caller(coroutine_context *src, const fcontext::fcontext_t &fctx)