RadosGW的Coroutine浅析
文章目录
参考资料
RadosGW使用boost::asio::coroutine的原因
之前,RadosGW所使用的模型都是多线程同步模型。比如处理S3/swift请求的就是rgw_thread_pool_size
个线程,每个线程在处理完一个请求前,被完全占用,不能处理其他请求。
RGWCoroutine是社区在实现跨区域复制(multisite)的时候引入的。根据multisite的原理,ZoneGroup里有N个Zone,每个Zone里有M个datalog shard的话,按照原来的多线程模型,每个RGW需要O(N*M)
个线程来负责与其他N-1个Zone之间的数据同步。而从代码看,社区只想用一个元数据同步线程和N个数据同步线程就完成成语N个Zone的同步。这就意味着一个线程需要有能力在User Mode做到不同的同步任务之间切换,从而虚拟出多线程的假象。
一般情况下,程序员并不需要在一个C++函数中标记说,哪些位置可以进行线程切换。做线程切换的决策完全交给Kernel Mode处理。线程上下文的保存和切换也是Kernel Mode自动完成。而在multisite实现中,由于需要实际的线程数有限,不能让一个函数完全霸占,需要在适当的时候,User Mode识别出函数在哪里可以搞一段落,暂时中断,过一会儿再执行。boost::asio::coroutine
中一个个yield语句,本质上就是做这样的标记。RGWCoroutine::operate()
中每个yield
在boost::asio::coroutine
的成员变量中记录下当前的执行位置,并使RGWCoroutine::operate()
返回,这时,真实的线程就可以先去处理其他任务,过一会儿再来调用这个RGWCoroutine::operate()时,根据boost::asio::coroutine
的标记,再继续执行。所以,boost::asio::coroutine
是用来在User Mode实现上下文的保存的工具。
RadosGW基于Coroutine的用户态操作系统
组件映射关系
RadosGW中的概念 | 操作系统中的概念 |
---|---|
RGWCoroutine | Function/Method |
RGWCoroutinsStack | Thread |
Thread | CPU Core |
context | Process |
RGWCoroutinesEnv | Context-related Registers |
RGWCoroutinesManager | Thread Scheduler |
RGWCompletionManager | Timer/Mutex |
RGWCoroutine是一个抽象类,multisite的代码中大量定义RGWCoroutine的子类,以重载其RGWCoroutine::operate()
方法,从而实现不同的功能。RGWCoroutine相当于C++中的functor,不同的是它利用boost::asio::coroutine
技术在User Mode将operate()方法切割成了多个可以中断的部分。另外,RGWCoroutine::operate()
的返回值是int类型,不支持其他类型的返回值。
RGWCoroutinsStack
对应的是线程的概念。这里叫XXXStack也是合理,因为其主体内容其实就是一系列RGWCoroutine
组成的一个Stack,执行顺序和传统意义上的函数调用栈也是一致的。
RGWCoroutine::call()
本质上就是将一个RGWCoroutine
放置到当前RGWCoroutinsStack
的顶端,就类似于传统的函数调用。RGWCoroutine::spawn()
本质上就是创建一个新的RGWCoroutinsStack
,并将一个RGWCoroutine
放在新的RGWCoroutinsStack
的顶部,就类似于传统的pthread_create()
。
multisite中实体的Thread主要是RGWSyncProcessorThread
的子类RGWMetaSyncProcessorThread
和RGWDataSyncProcessorThread
,这些线程最终调用了RGWCoroutinesManager::run()
以达到用RGWCoroutine
模拟更多线程的目的。这些线程可类比CPU的core。
context是存放在RGWCoroutinesManager::run_contexts
里。RGWCoroutinesManager::run_contexts
是一个Map,key是整数,value是set<RGWCoroutinesStack *>
。目前看来这个概念虽然和进程对的上号,但multisite并没有使用到多个context的情况。
RGWCoroutinesEnv是RGWCoroutinesManager::run(list<RGWCoroutinesStack *>& stacks)
中的一个局部变量,用来记住当前正在处理的是哪个context(对应进程),改context中哪些RGWCoroutinsStack
正要被执行(对应Ready的线程),当前正在执行的是那个RGWCoroutinsStack
(对应当前线程)。
RGWCoroutinesManager
的主要逻辑在RGWCoroutinesManager::run(list<RGWCoroutinesStack *>& stacks)
,这个成员函数负责不停地将可以执行的RGWCoroutinsStack
找出来,并执行其顶部的RGWCoroutine
。
在传统的多线程难免需要sleep或等待其他线程完成特定操作的情况,RGWCompletionManager
负责为RGWCoroutine
提供这些基础设施。
RGWCoroutinesManager::run()
RGWCoroutinesManager::run(list<RGWCoroutinesStack *>& stacks)
是用户态操作系统的核心逻辑,简单说它就是创建了一个虚拟进程(context
),然后运行里面的虚拟线程(RGWCoroutinsStack
),知道这个虚拟进程结束。这里对这个代码进行一下注释:
// 这里传进来的stacks可认为是一组初始的等待执行的线程,一般只有一个RGWCoroutinesStack,即相当于那个主线程
int RGWCoroutinesManager::run(list<RGWCoroutinesStack *>& stacks)
{
int ret = 0;
int blocked_count = 0;
int interval_wait_count = 0;
bool canceled = false; // set on going_down
// 定义RGWCoroutinesEnv这个局部变量
RGWCoroutinesEnv env;
// 申请一个新的context编号,一般从run_contexts获取对应的set<RGWCoroutinesStack *>
// 由于run_context_count的初始值为0,所以context编号>0
uint64_t run_context = ++run_context_count;
lock.get_write();
// 初始化RGWCoroutinesEnv的各个成员变量,其中scheduled_stacks类似于操作系统中的ready线程的队列
set<RGWCoroutinesStack *>& context_stacks = run_contexts[run_context];
list<RGWCoroutinesStack *> scheduled_stacks;
for (auto& st : stacks) {
context_stacks.insert(st);
scheduled_stacks.push_back(st);
}
lock.unlock();
env.run_context = run_context;
env.manager = this;
env.scheduled_stacks = &scheduled_stacks;
// 相当于在操作系统里不停遍历ready线程的队列,对线程进行执行
for (list<RGWCoroutinesStack *>::iterator iter = scheduled_stacks.begin(); iter != scheduled_stacks.end() && !going_down;) {
lock.get_write();
RGWCoroutinesStack *stack = *iter;
// 修改env.stack指向当前的虚拟线程
env.stack = stack;
// 执行虚拟线程顶部的RGWCoroutine的一部分
ret = stack->operate(&env);
stack->set_is_scheduled(false);
if (ret < 0) {
ldout(cct, 20) << "stack->operate() returned ret=" << ret << dendl;
}
if (stack->is_error()) {
report_error(stack);
}
bool op_not_blocked = false;
if (stack->is_io_blocked()) {
// 记录一些统计信息,打些日志
ldout(cct, 20) << __func__ << ":" << " stack=" << (void *)stack << " is io blocked" << dendl;
if (stack->is_interval_waiting()) {
interval_wait_count++;
}
blocked_count++;
} else if (stack->is_blocked()) {
/* do nothing, we'll re-add the stack when the blocking stack is done,
* or when we're awaken
*/
// 打些日志
ldout(cct, 20) << __func__ << ":" << " stack=" << (void *)stack << " is_blocked_by_stack()=" << stack->is_blocked_by_stack()
<< " is_sleeping=" << stack->is_sleeping() << " waiting_for_child()=" << stack->waiting_for_child() << dendl;
} else if (stack->is_done()) {
ldout(cct, 20) << __func__ << ":" << " stack=" << (void *)stack << " is done" << dendl;
RGWCoroutinesStack *s;
// 当前虚拟线程执行结束,看看有没有其他虚拟线程正在等待本线程,有的话调度一下,使其能够继续执行
while (stack->unblock_stack(&s)) {
if (!s->is_blocked_by_stack() && !s->is_done()) {
if (s->is_io_blocked()) {
if (stack->is_interval_waiting()) {
interval_wait_count++;
}
blocked_count++;
} else {
s->schedule();
}
}
}
// 如果否父虚拟线程(即RGWCoroutine::spawn出本虚拟线程的那个虚拟线程)等待本线程结束,
// 那么激活那个父虚拟线程
if (stack->parent && stack->parent->waiting_for_child()) {
stack->parent->set_wait_for_child(false);
stack->parent->schedule();
}
// 既然本虚拟线程执行完了,就从context_stacks清理掉,消减引用数
context_stacks.erase(stack);
stack->put();
stack = NULL;
} else {
// 本虚拟线程还有RGWCoroutine需要执行,而且目前没有被阻塞,那就再加入队列中,等待执行
op_not_blocked = true;
stack->run_count++;
stack->schedule();
}
if (!op_not_blocked && stack) {
stack->run_count = 0;
}
lock.unlock();
RGWCoroutinesStack *blocked_stack;
// 检查一下有没有等待定时器或其他原因被阻塞的虚拟线程已经可以被唤醒了,有的话拿出来,加入执行队列里
// 注意这里的try_get_next是非阻塞的,当没有可以被唤醒的虚拟线程时,立刻返回false
while (completion_mgr->try_get_next((void **)&blocked_stack)) {
handle_unblocked_stack(context_stacks, scheduled_stacks, blocked_stack, &blocked_count);
}
/*
* only account blocked operations that are not in interval_wait, these are stacks that
* were put on a wait without any real IO operations. While we mark these as io_blocked,
* these aren't really waiting for IOs
*/
// 当被IO阻塞的虚拟线程太多时,则尽可能等待,以消减被阻塞的虚拟线程
while (blocked_count - interval_wait_count >= ops_window) {
// 注意这里的get_next和try_get_next不同
// get_next如果发现没有虚拟线程可以被唤醒时,会一直阻塞到至少一个虚拟线程可以被唤醒
ret = completion_mgr->get_next((void **)&blocked_stack);
if (ret < 0) {
ldout(cct, 5) << "completion_mgr.get_next() returned ret=" << ret << dendl;
}
handle_unblocked_stack(context_stacks, scheduled_stacks, blocked_stack, &blocked_count);
}
// 准备执行下一个虚拟线程
++iter;
// 将刚执行的虚拟线程剔出队列
scheduled_stacks.pop_front();
// 如果ready队列里没有可执行的虚拟线程了,而且还有虚拟线程被阻塞,那么就一直等待到有线程能被唤醒
while (scheduled_stacks.empty() && blocked_count > 0) {
ret = completion_mgr->get_next((void **)&blocked_stack);
if (ret < 0) {
ldout(cct, 5) << "completion_mgr.get_next() returned ret=" << ret << dendl;
}
if (going_down) {
ldout(cct, 5) << __func__ << "(): was stopped, exiting" << dendl;
ret = -ECANCELED;
canceled = true;
break;
}
handle_unblocked_stack(context_stacks, scheduled_stacks, blocked_stack, &blocked_count);
iter = scheduled_stacks.begin();
}
if (canceled) {
break;
}
// 到达队尾,从头开始执行虚拟线程
if (iter == scheduled_stacks.end()) {
iter = scheduled_stacks.begin();
}
}
lock.get_write();
// 打印些日志
if (!context_stacks.empty() && !going_down) {
JSONFormatter formatter(true);
formatter.open_array_section("context_stacks");
for (auto& s : context_stacks) {
::encode_json("entry", *s, &formatter);
}
formatter.close_section();
lderr(cct) << __func__ << "(): ERROR: deadlock detected, dumping remaining coroutines:\n";
formatter.flush(*_dout);
*_dout << dendl;
assert(context_stacks.empty() || going_down); // assert on deadlock
}
// 虚拟进程退出前,清理一下虚拟线程
for (auto stack : context_stacks) {
ldout(cct, 20) << "clearing stack on run() exit: stack=" << (void *)stack << " nref=" << stack->get_nref() << dendl;
stack->put();
}
// 清理掉虚拟进程
run_contexts.erase(run_context);
lock.unlock();
return ret;
}