libcopp 2.3.1
Loading...
Searching...
No Matches
coroutine_context_fiber.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#include <assert.h>
14#include <algorithm>
15#include <cstdlib>
16#include <cstring>
17// clang-format off
18#include <libcopp/utils/config/stl_include_suffix.h> // NOLINT(build/include_order)
19// clang-format on
20
21#if defined(LIBCOPP_MACRO_ENABLE_WIN_FIBER) && LIBCOPP_MACRO_ENABLE_WIN_FIBER
22LIBCOPP_COPP_NAMESPACE_BEGIN
23struct fiber_context_tls_data_t {
24 using jump_src_data_t = coroutine_context_fiber::jump_src_data_t;
25
26 LPVOID thread_fiber;
27 jump_src_data_t jump_data;
28 fiber_context_tls_data_t() : thread_fiber(nullptr) {
29 jump_data.from_co = nullptr;
30 jump_data.from_fiber = nullptr;
31 jump_data.to_co = nullptr;
32 jump_data.to_fiber = nullptr;
33 jump_data.priv_data = nullptr;
34 }
35 ~fiber_context_tls_data_t() {
36 if (thread_fiber) {
37 ConvertFiberToThread();
38 thread_fiber = nullptr;
39 }
40 }
41};
42
43# if LIBCOPP_MACRO_ENABLE_MULTI_THREAD
44static LIBCOPP_MACRO_THREAD_LOCAL fiber_context_tls_data_t gt_current_fiber;
45# else
46static fiber_context_tls_data_t gt_current_fiber;
47# endif
48
49static inline LPVOID get_this_fiber_address() {
50 if (!gt_current_fiber.thread_fiber) {
51 gt_current_fiber.thread_fiber = ConvertThreadToFiber(nullptr);
52 }
53
54 return gt_current_fiber.thread_fiber;
55}
56
57static inline fiber_context_tls_data_t::jump_src_data_t &get_this_fiber_jump_src() {
58 return gt_current_fiber.jump_data;
59}
60
61struct libcopp_fiber_internal_api_set {
62 using jump_src_data_t = coroutine_context_fiber::jump_src_data_t;
63
64 static inline fiber_context_tls_data_t::jump_src_data_t &build_this_fiber_jump_src(coroutine_context_fiber &to_ctx,
65 void *data) {
66 fiber_context_tls_data_t::jump_src_data_t &jump_src = get_this_fiber_jump_src();
67
68 jump_src.from_co = ::LIBCOPP_COPP_NAMESPACE_ID::this_fiber::get_coroutine();
69 if (nullptr == jump_src.from_co) {
70 jump_src.from_fiber = get_this_fiber_address();
71 } else {
72 jump_src.from_fiber = jump_src.from_co->callee_;
73 }
74
75 jump_src.to_co = &to_ctx;
76 jump_src.to_fiber = to_ctx.callee_;
77 jump_src.priv_data = data;
78 return jump_src;
79 }
80
81 LIBCOPP_UTIL_FORCEINLINE static void set_caller(coroutine_context_fiber *src, LPVOID fctx) {
82 if (nullptr != src) {
83 src->caller_ = fctx;
84 }
85 }
86
87 // LIBCOPP_UTIL_FORCEINLINE static void set_callee(coroutine_context_fiber *src, LPVOID fctx) {
88 // if (nullptr != src) {
89 // src->callee_ = fctx;
90 // }
91 // }
92
93 static void __stdcall coroutine_fiber_context_callback(LPVOID lpParameter) {
94 coroutine_context_fiber *ctx = reinterpret_cast<coroutine_context_fiber *>(lpParameter);
95 assert(ctx);
96 if (nullptr == ctx) {
97 abort();
98 // return; // clang-analyzer will report "Unreachable code"
99 }
100
101 // copy jump_src_data_t in case it's destroyed later
102 jump_src_data_t jump_src = get_this_fiber_jump_src();
103
104 // this must in a coroutine
105 coroutine_context_fiber *ins_ptr = jump_src.to_co;
106 assert(ins_ptr == ctx);
107 if (ctx != ins_ptr) {
108 abort();
109 // return; // clang-analyzer will report "Unreachable code"
110 }
111
112 // update caller of to_co
113 ins_ptr->caller_ = jump_src.from_fiber;
114
115 // There is no need to update fiber's callee_ of from_co
116 // if (nullptr != jump_src.from_co) {
117 // jump_src.from_co->callee_ = jump_src.from_fiber;
118 // }
119
120 // this_fiber
122
123 // run logic code
124# if defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
125 try {
126# endif
127 ins_ptr->run_and_recv_retcode(jump_src.priv_data);
128# if defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
129 } catch (...) {
130 ins_ptr->unhandle_exception_ = std::current_exception();
131 }
132# endif
133
134 ins_ptr->flags_ |= coroutine_context_fiber::flag_type::EN_CFT_FINISHED;
135 // add memory fence to flush flags_(used in is_finished())
136 // LIBCOPP_UTIL_LOCK_ATOMIC_THREAD_FENCE(LIBCOPP_COPP_NAMESPACE_ID::util::lock::memory_order_release);
137 ins_ptr->yield();
138 }
139};
145static inline void jump_to(LPVOID to_fiber,
146 libcopp_fiber_internal_api_set::jump_src_data_t &jump_src) LIBCOPP_MACRO_NOEXCEPT {
147 coroutine_context_fiber *restore_co = jump_src.from_co;
148
149 SwitchToFiber(to_fiber);
150
151 // Fiber may be yield by another thread, we can not use jump_src_data_t from parameter here
152 jump_src = get_this_fiber_jump_src();
153
167 // update caller of to_co if not jump from yield
168 libcopp_fiber_internal_api_set::set_caller(jump_src.to_co, jump_src.from_fiber);
169
170 // There is no need to update fiber's callee_ of from_co
171 // libcopp_fiber_internal_api_set::set_callee(jump_src->from_co, res.fctx);
172
173 // this_fiber
175}
176
177LIBCOPP_COPP_API coroutine_context_fiber::coroutine_context_fiber() LIBCOPP_MACRO_NOEXCEPT : coroutine_context_base(),
178 caller_(nullptr),
179 callee_(nullptr),
180 callee_stack_() {
181 flags_ |= flag_type::EN_CFT_IS_FIBER;
182 // set_flags(flag_type::EN_CFT_IS_FIBER); // can not use set_flags to set a coroutine context's flag here
183}
184
185LIBCOPP_COPP_API coroutine_context_fiber::~coroutine_context_fiber() {
186 if (nullptr != callee_) {
187 DeleteFiber(callee_);
188 }
189}
190
191LIBCOPP_COPP_API int coroutine_context_fiber::create(coroutine_context_fiber *p, callback_type &&runner,
192 const stack_context &callee_stack, size_t coroutine_size,
193 size_t private_buffer_size,
194 size_t stack_reserve_size_of_fiber) LIBCOPP_MACRO_NOEXCEPT {
195 if (nullptr == p) {
196 return COPP_EC_ARGS_ERROR;
197 }
198
199 // must aligned to sizeof(size_t)
200 if (0 != (private_buffer_size & (sizeof(size_t) - 1))) {
201 return COPP_EC_ARGS_ERROR;
202 }
203
204 if (0 != (coroutine_size & (sizeof(size_t) - 1))) {
205 return COPP_EC_ARGS_ERROR;
206 }
207
208 size_t stack_offset = private_buffer_size + coroutine_size;
209 if (nullptr == callee_stack.sp || callee_stack.size <= stack_offset) {
210 return COPP_EC_ARGS_ERROR;
211 }
212
213 // stack down
214 // |STACK BUFFER........COROUTINE..this..padding..PRIVATE DATA.....callee_stack.sp |
215 // |------------------------------callee_stack.size -------------------------------|
216 if (callee_stack.sp <= p || coroutine_size < sizeof(coroutine_context_fiber)) {
217 return COPP_EC_ARGS_ERROR;
218 }
219
220 size_t this_offset =
221 static_cast<size_t>(reinterpret_cast<unsigned char *>(callee_stack.sp) - reinterpret_cast<unsigned char *>(p));
222 if (this_offset < sizeof(coroutine_context_fiber) + private_buffer_size || this_offset > stack_offset) {
223 return COPP_EC_ARGS_ERROR;
224 }
225
226 // if runner is empty, we can set it later
227 p->set_runner(std::move(runner));
228
229 if (&p->callee_stack_ != &callee_stack) {
230 p->callee_stack_ = callee_stack;
231 }
232 p->private_buffer_size_ = private_buffer_size;
233
234 // stack down, left enough private data
235 p->priv_data_ = reinterpret_cast<unsigned char *>(p->callee_stack_.sp) - p->private_buffer_size_;
236 p->callee_ =
237 CreateFiberEx(0, stack_reserve_size_of_fiber,
238 0, // We don't use FIBER_FLAG_FLOAT_SWITCH because fcontext version also don't save XMM0-XMM7
239 &libcopp_fiber_internal_api_set::coroutine_fiber_context_callback, p);
240 if (nullptr == p->callee_) {
242 }
243
244 return COPP_EC_SUCCESS;
245}
246
247# if defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
248LIBCOPP_COPP_API int coroutine_context_fiber::start(void *priv_data) {
249 std::exception_ptr eptr;
250 int ret = start(eptr, priv_data);
251 maybe_rethrow(eptr);
252 return ret;
253}
254
255LIBCOPP_COPP_API int coroutine_context_fiber::start(std::exception_ptr &unhandled,
256 void *priv_data) LIBCOPP_MACRO_NOEXCEPT {
257# else
258LIBCOPP_COPP_API int coroutine_context_fiber::start(void *priv_data) {
259# endif
260 if (nullptr == callee_) {
261 return COPP_EC_NOT_INITED;
262 }
263
265 if (this_ctx && !this_ctx->check_flags(flag_type::EN_CFT_IS_FIBER)) {
266 return LIBCOPP_COPP_NAMESPACE_ID::COPP_EC_CAN_NOT_USE_CROSS_FCONTEXT_AND_FIBER;
267 }
268
269 int from_status = status_type::EN_CRS_READY;
270 do {
271 if (from_status < status_type::EN_CRS_READY) {
272 return COPP_EC_NOT_INITED;
273 }
274
275 if (status_.compare_exchange_strong(from_status, status_type::EN_CRS_RUNNING,
276 LIBCOPP_COPP_NAMESPACE_ID::util::lock::memory_order_acq_rel,
277 LIBCOPP_COPP_NAMESPACE_ID::util::lock::memory_order_acquire)) {
278 break;
279 } else {
280 // finished or stoped
281 if (from_status > status_type::EN_CRS_RUNNING) {
282 return COPP_EC_NOT_READY;
283 }
284
285 // already running
286 if (status_type::EN_CRS_RUNNING == from_status) {
287 return COPP_EC_IS_RUNNING;
288 }
289 }
290 } while (true);
291
292 libcopp_fiber_internal_api_set::jump_src_data_t jump_src =
293 libcopp_fiber_internal_api_set::build_this_fiber_jump_src(*this, priv_data);
294 jump_to(callee_, jump_src);
295
296 // Move changing status to EN_CRS_EXITED is finished
297 if (check_flags(flag_type::EN_CFT_FINISHED)) {
298 // if in finished status, change it to exited
299 status_.store(status_type::EN_CRS_EXITED, LIBCOPP_COPP_NAMESPACE_ID::util::lock::memory_order_release);
300 }
301
302# if defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
303 if LIBCOPP_UTIL_UNLIKELY_CONDITION (unhandle_exception_) {
304 std::swap(unhandled, unhandle_exception_);
305 }
306# endif
307
308 return COPP_EC_SUCCESS;
309}
310
311LIBCOPP_COPP_API int coroutine_context_fiber::resume(void *priv_data) { return start(priv_data); }
312# if defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
313LIBCOPP_COPP_API int coroutine_context_fiber::resume(std::exception_ptr &unhandled,
314 void *priv_data) LIBCOPP_MACRO_NOEXCEPT {
315 return start(unhandled, priv_data);
316}
317# endif
318
319LIBCOPP_COPP_API int coroutine_context_fiber::yield(void **priv_data) LIBCOPP_MACRO_NOEXCEPT {
320 if (nullptr == callee_) {
321 return COPP_EC_NOT_INITED;
322 }
323
324 int from_status = status_type::EN_CRS_RUNNING;
325 int to_status = status_type::EN_CRS_READY;
326 if (check_flags(flag_type::EN_CFT_FINISHED)) {
327 to_status = status_type::EN_CRS_FINISHED;
328 }
329 if (false == status_.compare_exchange_strong(from_status, to_status,
330 LIBCOPP_COPP_NAMESPACE_ID::util::lock::memory_order_acq_rel,
331 LIBCOPP_COPP_NAMESPACE_ID::util::lock::memory_order_acquire)) {
332 switch (from_status) {
333 case status_type::EN_CRS_INVALID:
334 return COPP_EC_NOT_INITED;
335 case status_type::EN_CRS_READY:
336 return COPP_EC_NOT_RUNNING;
337 case status_type::EN_CRS_FINISHED:
338 case status_type::EN_CRS_EXITED:
340 default:
341 return COPP_EC_UNKNOWN;
342 }
343 }
344
345 // success or finished will continue
346 jump_src_data_t jump_data;
347 jump_data.from_co = this;
348 jump_data.from_fiber = callee_;
349 jump_data.to_co = nullptr;
350 jump_data.to_fiber = caller_;
351 jump_data.priv_data = nullptr;
352
353 jump_to(caller_, jump_data);
354
355 if (nullptr != priv_data) {
356 *priv_data = jump_data.priv_data;
357 }
358
359 return COPP_EC_SUCCESS;
360}
361
362namespace this_fiber {
363LIBCOPP_COPP_API coroutine_context_fiber *get_coroutine() LIBCOPP_MACRO_NOEXCEPT {
366 ret = nullptr;
367 }
368 return static_cast<coroutine_context_fiber *>(ret);
369}
370
371LIBCOPP_COPP_API int yield(void **priv_data) LIBCOPP_MACRO_NOEXCEPT {
372 coroutine_context_fiber *pco = get_coroutine();
373 if (nullptr != pco) {
374 return pco->yield(priv_data);
375 }
376
377 return COPP_EC_NOT_RUNNING;
378}
379} // namespace this_fiber
380LIBCOPP_COPP_NAMESPACE_END
381
382#endif
base type of all coroutine context
static LIBCOPP_COPP_API void set_this_coroutine_base(coroutine_context_base *ctx) LIBCOPP_MACRO_NOEXCEPT
set current coroutine
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
#define LIBCOPP_UTIL_FORCEINLINE
#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_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
constexpr auto data(TCONTAINER &&container) -> decltype(container.data())
Definition span.h:54
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