libcopp  2.2.0
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 
5 #include <libcopp/utils/errno.h>
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
22 LIBCOPP_COPP_NAMESPACE_BEGIN
23 struct 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 defined(LIBCOPP_LOCK_DISABLE_THIS_MT) && LIBCOPP_LOCK_DISABLE_THIS_MT
44 static fiber_context_tls_data_t gt_current_fiber;
45 # else
46 static COPP_MACRO_THREAD_LOCAL fiber_context_tls_data_t gt_current_fiber;
47 # endif
48 
49 static 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 
57 static inline fiber_context_tls_data_t::jump_src_data_t &get_this_fiber_jump_src() {
58  return gt_current_fiber.jump_data;
59 }
60 
61 struct 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 
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  UTIL_FORCEINLINE static void set_caller(coroutine_context_fiber *src, LPVOID fctx) {
82  if (nullptr != src) {
83  src->caller_ = fctx;
84  }
85  }
86 
87  // 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 };
145 static 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 
177 LIBCOPP_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 
185 LIBCOPP_COPP_API coroutine_context_fiber::~coroutine_context_fiber() {
186  if (nullptr != callee_) {
187  DeleteFiber(callee_);
188  }
189 }
190 
191 LIBCOPP_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 = reinterpret_cast<unsigned char *>(callee_stack.sp) - reinterpret_cast<unsigned char *>(p);
221  if (this_offset < sizeof(coroutine_context_fiber) + private_buffer_size || this_offset > stack_offset) {
222  return COPP_EC_ARGS_ERROR;
223  }
224 
225  // if runner is empty, we can set it later
226  p->set_runner(std::move(runner));
227 
228  if (&p->callee_stack_ != &callee_stack) {
229  p->callee_stack_ = callee_stack;
230  }
231  p->private_buffer_size_ = private_buffer_size;
232 
233  // stack down, left enough private data
234  p->priv_data_ = reinterpret_cast<unsigned char *>(p->callee_stack_.sp) - p->private_buffer_size_;
235  p->callee_ =
236  CreateFiberEx(0, stack_reserve_size_of_fiber,
237  0, // We don't use FIBER_FLAG_FLOAT_SWITCH because fcontext version also don't save XMM0-XMM7
238  &libcopp_fiber_internal_api_set::coroutine_fiber_context_callback, p);
239  if (nullptr == p->callee_) {
241  }
242 
243  return COPP_EC_SUCCESS;
244 }
245 
246 # if defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
247 LIBCOPP_COPP_API int coroutine_context_fiber::start(void *priv_data) {
248  std::exception_ptr eptr;
249  int ret = start(eptr, priv_data);
250  maybe_rethrow(eptr);
251  return ret;
252 }
253 
254 LIBCOPP_COPP_API int coroutine_context_fiber::start(std::exception_ptr &unhandled,
255  void *priv_data) LIBCOPP_MACRO_NOEXCEPT {
256 # else
257 LIBCOPP_COPP_API int coroutine_context_fiber::start(void *priv_data) {
258 # endif
259  if (nullptr == callee_) {
260  return COPP_EC_NOT_INITED;
261  }
262 
264  if (this_ctx && !this_ctx->check_flags(flag_type::EN_CFT_IS_FIBER)) {
266  }
267 
268  int from_status = status_type::EN_CRS_READY;
269  do {
270  if (from_status < status_type::EN_CRS_READY) {
271  return COPP_EC_NOT_INITED;
272  }
273 
274  if (status_.compare_exchange_strong(from_status, status_type::EN_CRS_RUNNING,
277  break;
278  } else {
279  // finished or stoped
280  if (from_status > status_type::EN_CRS_RUNNING) {
281  return COPP_EC_NOT_READY;
282  }
283 
284  // already running
285  if (status_type::EN_CRS_RUNNING == from_status) {
286  return COPP_EC_IS_RUNNING;
287  }
288  }
289  } while (true);
290 
291  libcopp_fiber_internal_api_set::jump_src_data_t jump_src =
292  libcopp_fiber_internal_api_set::build_this_fiber_jump_src(*this, priv_data);
293  jump_to(callee_, jump_src);
294 
295  // Move changing status to EN_CRS_EXITED is finished
296  if (check_flags(flag_type::EN_CFT_FINISHED)) {
297  // if in finished status, change it to exited
298  status_.store(status_type::EN_CRS_EXITED, LIBCOPP_COPP_NAMESPACE_ID::util::lock::memory_order_release);
299  }
300 
301 # if defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
302  COPP_UNLIKELY_IF (unhandle_exception_) {
303  std::swap(unhandled, unhandle_exception_);
304  }
305 # endif
306 
307  return COPP_EC_SUCCESS;
308 }
309 
310 LIBCOPP_COPP_API int coroutine_context_fiber::resume(void *priv_data) { return start(priv_data); }
311 # if defined(LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR) && LIBCOPP_MACRO_ENABLE_STD_EXCEPTION_PTR
312 LIBCOPP_COPP_API int coroutine_context_fiber::resume(std::exception_ptr &unhandled,
313  void *priv_data) LIBCOPP_MACRO_NOEXCEPT {
314  return start(unhandled, priv_data);
315 }
316 # endif
317 
318 LIBCOPP_COPP_API int coroutine_context_fiber::yield(void **priv_data) LIBCOPP_MACRO_NOEXCEPT {
319  if (nullptr == callee_) {
320  return COPP_EC_NOT_INITED;
321  }
322 
323  int from_status = status_type::EN_CRS_RUNNING;
324  int to_status = status_type::EN_CRS_READY;
325  if (check_flags(flag_type::EN_CFT_FINISHED)) {
326  to_status = status_type::EN_CRS_FINISHED;
327  }
328  if (false == status_.compare_exchange_strong(from_status, to_status,
331  switch (from_status) {
332  case status_type::EN_CRS_INVALID:
333  return COPP_EC_NOT_INITED;
334  case status_type::EN_CRS_READY:
335  return COPP_EC_NOT_RUNNING;
336  case status_type::EN_CRS_FINISHED:
337  case status_type::EN_CRS_EXITED:
338  return COPP_EC_ALREADY_EXIST;
339  default:
340  return COPP_EC_UNKNOWN;
341  }
342  }
343 
344  // success or finished will continue
345  jump_src_data_t jump_data;
346  jump_data.from_co = this;
347  jump_data.from_fiber = callee_;
348  jump_data.to_co = nullptr;
349  jump_data.to_fiber = caller_;
350  jump_data.priv_data = nullptr;
351 
352  jump_to(caller_, jump_data);
353 
354  if (nullptr != priv_data) {
355  *priv_data = jump_data.priv_data;
356  }
357 
358  return COPP_EC_SUCCESS;
359 }
360 
361 namespace this_fiber {
362 LIBCOPP_COPP_API coroutine_context_fiber *get_coroutine() LIBCOPP_MACRO_NOEXCEPT {
365  ret = nullptr;
366  }
367  return static_cast<coroutine_context_fiber *>(ret);
368 }
369 
370 LIBCOPP_COPP_API int yield(void **priv_data) LIBCOPP_MACRO_NOEXCEPT {
371  coroutine_context_fiber *pco = get_coroutine();
372  if (nullptr != pco) {
373  return pco->yield(priv_data);
374  }
375 
376  return COPP_EC_NOT_RUNNING;
377 }
378 } // namespace this_fiber
379 LIBCOPP_COPP_NAMESPACE_END
380 
381 #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 UTIL_FORCEINLINE
static void jump_to(fcontext::fcontext_t &to_fctx, EXPLICIT_UNUSED_ATTR stack_context &from_sctx, 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_CAN_NOT_USE_CROSS_FCONTEXT_AND_FIBER
COPP_EC_CAN_NOT_USE_CROSS_FCONTEXT_AND_FIBER.
Definition: errno.h:35
@ 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:34
@ 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
导入继承关系约束 Licensed under the MIT licenses.
#define COPP_UNLIKELY_IF(...)
Definition: features.h:117
constexpr auto data(TCONTAINER &&container) -> decltype(container.data())
Definition: span.h:54
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
void swap(intrusive_ptr< T > &lhs, intrusive_ptr< T > &rhs)