几个月前突然想看下协程相关的东西,于是下了demo跑了起来,测试代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include "co_routine.h"
#include<hircluster.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
using namespace std;
static void * testRedis(void *arg) {
co_enable_hook_sys();
int * i = (int *)arg;
const char *key="key-a";
const char *field="field-1";
const char *key1="key1";
const char *value1="value-1";
const char *key2="key1";
const char *value2="value-1";
redisClusterContext *cc;
cc = redisClusterContextInit();
redisClusterSetOptionAddNodes(cc, "192.168.122.185:30001,192.168.122.185:30002");
redisClusterConnect2(cc);
if(cc == NULL || cc->err)
{
}
redisReply *reply = (redisReply *)redisClusterCommand(cc, "hmget %s %s", key, field);
if(reply == NULL)
{
cout<< cc->errstr << endl; ;
redisClusterFree(cc);
}
}
int main(int argc,char *argv[]) {
int cnt = atoi( argv[1] );
int proccnt = atoi( argv[2] );
for(int i=0;i<cnt;i++){
stCoRoutine_t *co = 0;
co_create( &co,NULL, testRedis, &i);
co_resume( co );
}
co_eventloop( co_get_epoll_ct(),0,0 );
return 0;
}

刚跑起来就core了

(gdb) bt

#0 0x00007fdfdf04bc90 in ?? ()

#1 0x00007fdfdeb20d9c in co_eventloop (ctx=0x25cdac0, pfn=0x0, arg=0x0)
at co_routine.cpp:779

#2 0x000000000041021a in main (argc=3, argv=0x7ffcec531b28)
at /home/cai/soft/cppserver-c7/server/brocaststat/src/BrocastStat.cpp:58

1
2
3
4
5
6
7
8
9
10
11
12
for(int i=0;i<ret;i++)
{
stTimeoutItem_t *item = (stTimeoutItem_t*)result->events[i].data.ptr;
if( item->pfnPrepare )
{
item->pfnPrepare( item,result->events[i],active );//出问题代码
}
else
{
AddTail( active,item );
}
}

这个是libco的内部函数,于是debug了下,发现初始化的时候地址是正常的,但是经过协程切换后这个函数地址的前几位被截断了,导致了地址失效。于是跑libco的example,一切正常,函数地址没有被截断。
所以问题就缩小到了切换协程的地方了,但是暂时没啥头绪,也搜不到相关的资料(libco的资料实在太少了)。

问题定位

某天晚上闲的发慌于是就翻了libco的源码,发现在初始化的时候,协程栈只有128k,也没看到扩容的地方,于是联想到是不是爆栈了,于是调大了栈后问题真的没出现了

问题分析

Breakpoint 1, save_stack_buffer (occupy_co=0x636790) at co_routine.cpp:584
584 stStackMem_t* stack_mem = occupy_co->stack_mem;
Missing separate debuginfos, use: debuginfo-install apr-1.4.8-3.el7.x86_64 apr-util-1.5.2-6.el7.x86_64 cyrus-sasl-lib-2.1.26-20.el7_2.x86_64 expat-2.1.0-10.el7_3.x86_64 glibc-2.17-157.el7_3.5.x86_64 libdb-5.3.21-19.el7.x86_64 libgcc-4.8.5-11.el7.x86_64 libstdc++-4.8.5-11.el7.x86_64 libuuid-2.23.2-26.el7_2.3.x86_64 nspr-4.11.0-1.el7_2.x86_64 nss-3.21.0-9.el7_2.x86_64 nss-softokn-freebl-3.16.2.3-14.4.el7.x86_64 nss-util-3.21.0-2.2.el7_2.x86_64 openldap-2.4.40-9.el7_2.x86_64 protobuf-2.5.0-8.el7.x86_64 zlib-1.2.7-17.el7.x86_64
(gdb) n
585 int len = stack_mem->stack_bp - occupy_co->stack_sp;
(gdb) n
587 if (occupy_co->save_buffer)
(gdb) p len
$1 = 148777

断点打在保存协程栈的函数,发现len是148777也就是145.29004kb,但是实际执行的只有128k,所以当协程切换的时候128k之外的就会被截断。讲道理libco是优化下,做栈自动扩容的,这个能避免不少坑,或者抛个异常到上层终止程序也好。