最近一直没什么时间整理近期碰到的问题,今天思考了一下之前碰到的一个临时处理的BUG,顺便写点东西清理一下思路。
其实严格来说这个BUG更应该是一个流程试用问题,不过这个问题应该是需要能在协程库里检测并抛出错误来。
libcopp 的执行流程
这个BUG涉及最底层context的执行流程,这个协程库切入有两个接口(start和resume),截止目前为止,这两个接口其实是对等的,然后有一个切出接口(yield)。切入的时候需要记录调用者的执行上下文,用于在切出时上下文切出到哪里。问题是各种情况下的这个上下文保存的执行状态。
然后再来看上下文切换的代码,第一部分是首次切入的入口(调用start函数)。
void coroutine_context_base::coroutine_context_callback(::copp::fcontext::transfer_t src_ctx) { assert(src_ctx.data); if (NULL == src_ctx.data) { abort(); return; } // copy jump_src_data_t in case it's destroyed later jump_src_data_t jump_src = *reinterpret_cast<jump_src_data_t *>(src_ctx.data); // this must in a coroutine coroutine_context_base *ins_ptr = jump_src.to_co; assert(ins_ptr); if (NULL == ins_ptr) { abort(); return; } // update caller of to_co ins_ptr->caller_ = src_ctx.fctx; // save from_co's fcontext and switch status if (UTIL_CONFIG_NULLPTR != jump_src.from_co) { jump_src.from_co->callee_ = src_ctx.fctx; int from_status = status_t::EN_CRS_RUNNING; // from coroutine change status from running to ready jump_src.from_co->status_.compare_exchange_strong(from_status, status_t::EN_CRS_READY); } // this_coroutine set_this_coroutine_context(ins_ptr); // run logic code ins_ptr->run_and_recv_retcode(jump_src.priv_data); ins_ptr->status_.store(status_t::EN_CRS_FINISHED); // jump back to caller ins_ptr->yield(); } |
最重要的是 save from_co’s fcontext and switch status 下面的这一段。这里的作用就是切入后要把跳入前的上下文保存到来源的协程中。
然后是恢复的流程(调用resume/yield函数):
void coroutine_context_base::jump_to(fcontext::fcontext_t &to_fctx, stack_context &from_sctx, stack_context &to_sctx, jump_src_data_t &jump_transfer) UTIL_CONFIG_NOEXCEPT { copp::fcontext::transfer_t res; jump_src_data_t *jump_src; int from_status; bool swap_success; // can not use any more stack now // can not initialize those vars here #ifdef COPP_MACRO_USE_SEGMENTED_STACKS assert(&from_sctx != &to_sctx); // ROOT->A: jump_transfer.from_co == NULL, jump_transfer.to_co == A, from_sctx == A.caller_stack_, skip backup segments // A->B.start(): jump_transfer.from_co == A, jump_transfer.to_co == B, from_sctx == B.caller_stack_, backup segments // B.yield()->A: jump_transfer.from_co == B, jump_transfer.to_co == NULL, from_sctx == B.callee_stack_, skip backup segments if (UTIL_CONFIG_NULLPTR != jump_transfer.from_co) { __splitstack_getcontext(jump_transfer.from_co->callee_stack_.segments_ctx); if (&from_sctx != &jump_transfer.from_co->callee_stack_) { memcpy(&from_sctx.segments_ctx, &jump_transfer.from_co->callee_stack_.segments_ctx, sizeof(from_sctx.segments_ctx)); } } else { __splitstack_getcontext(from_sctx.segments_ctx); } __splitstack_setcontext(to_sctx.segments_ctx); #endif res = copp::fcontext::copp_jump_fcontext(to_fctx, &jump_transfer); if (NULL == res.data) { abort(); return; } jump_src = reinterpret_cast<jump_src_data_t *>(res.data); assert(jump_src); /** * save from_co's fcontext and switch status * we should use from_co in transfer_t, because it may not jump from jump_transfer.to_co * * if we jump sequence is A->B->C->A.resume(), and if this call is A->B, then * jump_src->from_co = C, jump_src->to_co = A, jump_transfer.from_co = A, jump_transfer.to_co = B * and now we should save the callee of C and set the caller of A = C * * if we jump sequence is A->B.yield()->A, and if this call is A->B, then * jump_src->from_co = B, jump_src->to_co = NULL, jump_transfer.from_co = A, jump_transfer.to_co = B * and now we should save the callee of B and should change the caller of A * */ // update caller of to_co if not jump from yield mode if (UTIL_CONFIG_NULLPTR != jump_src->to_co) { jump_src->to_co->caller_ = res.fctx; } if (UTIL_CONFIG_NULLPTR != jump_src->from_co) { jump_src->from_co->callee_ = res.fctx; from_status = jump_src->from_co->status_.load(); if (status_t::EN_CRS_RUNNING == from_status) { jump_src->from_co->status_.compare_exchange_strong(from_status, status_t::EN_CRS_READY); } else if (status_t::EN_CRS_FINISHED == from_status) { // if in finished status, change it to exited jump_src->from_co->status_.store(status_t::EN_CRS_EXITED); } } // private data jump_transfer.priv_data = jump_src->priv_data; // this_coroutine set_this_coroutine_context(jump_transfer.from_co); // resume running status of from_co if (NULL != jump_transfer.from_co) { from_status = jump_transfer.from_co->status_.load(); swap_success = false; while (!swap_success && status_t::EN_CRS_READY == from_status) { swap_success = jump_transfer.from_co->status_.compare_exchange_strong(from_status, status_t::EN_CRS_RUNNING); } } } |