Giter VIP home page Giter VIP logo

libco's Introduction

Libco

Libco is a c/c++ coroutine library that is widely used in WeChat services. It has been running on tens of thousands of machines since 2013.

By linking with libco, you can easily transform synchronous back-end service into coroutine service. The coroutine service will provide out-standing concurrency compare to multi-thread approach. With the system hook, You can easily coding in synchronous way but asynchronous executed.

You can also use co_create/co_resume/co_yield interfaces to create asynchronous back-end service. These interface will give you more control of coroutines.

By libco copy-stack mode, you can easily build a back-end service support tens of millions of tcp connection.


简介

libco是微信后台大规模使用的c/c++协程库,2013年至今稳定运行在微信后台的数万台机器上。

libco通过仅有的几个函数接口 co_create/co_resume/co_yield 再配合 co_poll,可以支持同步或者异步的写法,如线程库一样轻松。同时库里面提供了socket族函数的hook,使得后台逻辑服务几乎不用修改逻辑代码就可以完成异步化改造。

作者: sunnyxu([email protected]), leiffyli([email protected]), [email protected]([email protected]), sarlmolchen([email protected])

PS: 近期将开源PaxosStore,敬请期待。

libco的特性

  • 无需侵入业务逻辑,把多进程、多线程服务改造成协程服务,并发能力得到百倍提升;
  • 支持CGI框架,轻松构建web服务(New);
  • 支持gethostbyname、mysqlclient、ssl等常用第三库(New);
  • 可选的共享栈模式,单机轻松接入千万连接(New);
  • 完善简洁的协程编程接口
  • 类pthread接口设计,通过co_create、co_resume等简单清晰接口即可完成协程的创建与恢复;
  • __thread的协程私有变量、协程间通信的协程信号量co_signal (New);
  • 语言级别的lambda实现,结合协程原地编写并执行后台异步任务 (New);
  • 基于epoll/kqueue实现的小而轻的网络框架,基于时间轮盘实现的高性能定时器;

Build

$ cd /path/to/libco
$ make

or use cmake

$ cd /path/to/libco
$ mkdir build
$ cd build
$ cmake ..
$ make

libco's People

Contributors

amutu avatar bstsnail avatar dengoswei avatar dy2012 avatar firedtoad avatar kaiywen avatar leiffyli avatar ochapman avatar priest334 avatar riddance avatar super-long avatar walkerdu avatar wechatsunny avatar xiaozhuai avatar zagzhang avatar zhoudayang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

libco's Issues

关于accept的疑问

accept没有被HOOK吧.
在example_echosvr中启动后accept_routine的co_accept返回<0, 会不断调用co_accept.
为什么这么处理呢, 为什么不是accept_routine在co_accept没有连接进入时候挂起呢?

g_arrCoEnvPerThread[ 204800 ] 数组越界问题

如题,
g_arrCoEnvPerThread[ 204800 ] 数组下标为进程pid,当pid数值大于204800 时,将引起数组越界。
虽然概率不大,但可能存在这个问题。

已提交pull requests,请查看下。

多线程驱动同一个协程,thread_local问题

代码如下
`#include "co_routine.h"
#include "co_routine_inner.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <pthread.h>
#include <unistd.h>

stCoRoutine_t* co;

static thread_local int aaa;

void* co_fun(void *)
{
printf("co_fun AAAAAA\n");
printf("aaa 0x%x\n", &aaa);
co_yield(co);
printf("co_fun BBBBBB\n");
printf("aaa 0x%x\n", &aaa);
}

void* thread_fun(void *)
{
printf("thread_fun AAAAAA\n");
co_init_curr_thread_env();

printf("thread_fun BBBBBB\n");
co_resume(co);
printf("thread_fun CCCCCC\n");

return NULL;

}

int main(int argc, char** argvs)
{
co_create(&co, NULL, co_fun, NULL);
co_resume(co);

pthread_t tid;
pthread_create(&tid,NULL,thread_fun,0);

for(;;)
{
	sleep(1);
}

}`

aaa这个thread_local变量打印的值是一样的。
一个协程执行到一半yield了,然后由另一个线程继续执行(比如一个同步rpc调用场景),那么里面的thread_local变量会很危险,特别是一些第三方库使用了thread_local后,这个怎么破?

另外CMake中没有设置C++的编译选项,实际上整个项目都是按照O1编译的,上面的例子需要把C++的编译选项加上O2

想问一下为什么aSpec没有初始化?

首先感谢开源这么有价值的库。
在使用过程中我发现通过co_create创建一个协程上下文时,并没有初始化aSpec, 从而导致使用CO_ROUTINE_SPECIFIC 宏时会出现获取到一个未定义指针的异常。
我通过手动清零的方式规避了这个问题。
请问这个问题是bug还是有其他考量的地方?

libco 和 libcurl 使用问题

有一些业务代码是用libcurl写的,是同步模式,想利用libco改造为异步。
这种场景下,只是利用hook的方式就可以吗?还是需要修改业务代码?

目前这个使用这个库对原项目似乎还是有不小的侵入性

以example_echosvr.cpp代码为例,首先没有hook系统的accept函数,需要调用自命名的co_accept函数。其次在调用co_accept返回小于0的fd的时候,需要自己手动调用co_poll。

为什么这里不对系统的accept函数进行hook,同时在调用accept函数返回小于0fd的时候,库自己内部将这个fd加入poll中呢?因为会出现这种情况,一个服务使用的是另一个二进制库,这个二进制库负责对外接收连接,无法侵入修改它的代码来适配libco的代码。

同样的问题也存在read、write等调用中,还是这个example_echosvr.cpp代码,在处理协程中,首先需要使用者调用co_poll将需要进行read操作的fd加入poll中,仍然对原有的代码造成了侵入。

co_swap方法进行协程切换隐含bug

原实现:

       if (update_occupy_co && update_pending_co && update_occupy_co != update_pending_co)
       {
              ...
       }

当使用共享栈协程,且当有协程回收时,update_occupy_co有可能为NULL,此时进不了条件中执行协程栈恢复,建议改为:

       if (update_pending_co && update_occupy_co != update_pending_co)
       {
              ...
       }

Build on macOS 10.13 fails due to deprecated system call

syscall() has been deprecated and is not available on macOS 10.12.

Platform

  • macOS 10.13
  • clang-900.0.37

Behavior

.../libco/co_routine.cpp:125:9: warning: 'syscall' is
      deprecated: first deprecated in macOS 10.12 - syscall(2) is unsupported;
      please switch to a supported interface. For SYS_kdebug_trace use
      kdebug_signpost(). [-Wdeprecated-declarations]
                tid = syscall( SYS_gettid );
                      ^
/usr/include/unistd.h:745:6: note: 'syscall' has been explicitly marked
      deprecated here
int      syscall(int, ...);
         ^
1 warning generated.

用co_free销毁共享栈协程时产生内存泄漏

原实现:

void co_free( stCoRoutine_t *co )
{
    if (!co->cIsShareStack) 
    {    
        free(co->stack_mem->stack_buffer);
        free(co->stack_mem);
    }   
    free( co );
}

建议修改为:

void co_free( stCoRoutine_t *co )
{
    if (!co->cIsShareStack) 
    {    
        free(co->stack_mem->stack_buffer);
        free(co->stack_mem);
    } else {
        if (co->stack_mem->occupy_co == co) {
            co->stack_mem->occupy_co = NULL;
        }
        
        if (co->save_buffer)
        {
            free(co->save_buffer), co->save_buffer = NULL;
        }
    }
    free( co );
}

socket fd must less than 102400,otherwise will crash?

static inline rpchook_t * alloc_by_fd( int fd )
{
	if( fd > -1 && fd < (int)sizeof(g_rpchook_socket_fd) / (int)sizeof(g_rpchook_socket_fd[0]) )
	{
		rpchook_t *lp = (rpchook_t*)calloc( 1,sizeof(rpchook_t) );
		lp->read_timeout.tv_sec = 1;
		lp->write_timeout.tv_sec = 1;
		g_rpchook_socket_fd[ fd ] = lp;
		return lp;
	}
	return NULL;
}

if fd > 102400,return NULL,but caller not handle this,the program will crash?

int socket(int domain, int type, int protocol)
{
   HOOK_SYS_FUNC( socket );

   if( !co_is_enable_sys_hook() )
   {
   	return g_sys_socket_func( domain,type,protocol );
   }
   int fd = g_sys_socket_func(domain,type,protocol);
   if( fd < 0 )
   {
   	return fd;
   }

   rpchook_t *lp = alloc_by_fd( fd );
   lp->domain = domain;//if lp==NULL,will crash
   
   fcntl( fd, F_SETFL, g_sys_fcntl_func(fd, F_GETFL,0 ) );

   return fd;
}

存在内在泄漏问题

不管是通过在协程中返回 或调用co_free,co_release都会泄漏内存,协程退出后并不会释放协程的独享栈,栈拷贝和其他资源

libco的内存泄漏问题?

stStackMem_t* co_alloc_stackmem(unsigned int stack_size)
这个函数 malloc了 stStackMem_t结构体和 stack_size,两个 buffer

但是在void co_release( stCoRoutine_t *co ) 函数中,
仅仅free 了 协程 co指针,
co->stack_mem
co->stack_buffer
以上两个指针都没有释放,会导致内存泄漏?

该库的协程是否支持C++异常机制?

该库的协程是否支持C++异常机制?
据我所知,机器通常会在线程栈上构建有异常保护帧;按理如果协程能完整保护/恢复线程栈的话应该没问题,但是这个在该库中是设计特性呢 还是实现的特性,似乎不明确。以及如果某协程抛出未处理的异常,会使整个协程系统中止工作么?

还有假如在异步过程中抛出异常,协程是否会接收该异常呢? 譬如
co_routine code block piece:
...
try
{
co_xxx(); // 如果这里边有异常抛出,协程能接收到么
} catch(XxException& ex)
{
}

移植到window

------------------ coctx_swap.s ---------------------------
.CODE

coctx_swap PROC
;;lea rax,[rsp+8]
;;lea rsp,[rdi+112] ;;right

lea rax,[rsp+8]
lea rsp,[rcx+112] ;; rcx存放参数1;rdx存放参数2

push rax
push rbx
push rcx
push rdx
push [rax-8] ;;ret func addr
push rsi
push rdi
push rbp
push r8
push r9
push r12
push r13
push r14
push r15

;;mov rsp,rsi ;;right
mov rsp,rdx ;;test

pop r15
pop r14
pop r13
pop r12
pop r9
pop r8
pop rbp
pop rdi
pop rsi
pop rax ;;ret func addr
pop rdx
pop rcx
pop rbx
pop rsp

push rax

xor eax,eax
ret

coctx_swap ENDP

END

--------------------------- coctx.cpp / coctx_make---------------------------
#ifdef WINDOW
ctx->regs[kRSP] = sp - 8;
ctx->regs[kRETAddr] = (char*)pfn;

ctx->regs[kRCX] = (char*)s;
ctx->regs[kRDX] = (char*)s1;

#else
ctx->regs[kRSP] = sp - 8;
ctx->regs[kRETAddr] = (char*)pfn;

ctx->regs[kRDI] = (char*)s;
ctx->regs[kRSI] = (char*)s1;

#endif

把汇编改成这样,但运行时还是出现一些堆栈的问题,例如printf报错。
是否window还有什么特殊的汇编规则啊?

能否对外开放GetPid方法

建议对外开放GetPid方法

修改:
将co_hook_sys_call.cpp中的GetPid方法删除,将co_routine.cpp中的GetPid方法的static修饰去掉,并在co_routine.h中加入GetPid方法声明

epoll_wait 返回的时间不是固定1ms,有几率出现1-4秒的情况?

在某个携程里用了poll(NULL, 0, msleep);来做定时,结果发现定时不准。msleep=10000的情况下有时候会出现12秒的延迟执行。最后定位到co_epoll_wait,好像不是固定阻塞1毫秒,有时候会阻塞1-4秒。整个测试代码就一个协程,内容仅仅是打印时间在屏幕,不会出现执行忙的情况。

mac os系统中connect返回成功,实际是连接失败

在co_hook_sys_call.cpp的connect方法中,有一段代码如下:
if( pf.revents & POLLOUT ) //connect succ
{
errno = 0;
return 0;
}
在mac os下连接失败也能进入此条件中,建议修改为:
if( pf.revents & POLLOUT ) //connect succ
{
g_sys_connect_func( fd,address,address_len );
if (errno == EISCONN)
{
errno = 0;
return 0;
}
else
{
return -1;
}
}

get compile error on mac osx

get compile error on mac osx:

co_hook_sys_call.cpp:865:15: error: use of undeclared identifier 'gethostbyname_r'
while (ret = gethostbyname_r(name, host, __co_hostbuf_wrap->buffer,

I can't find symbol 'gethostbyname_r' on whole proj.

Made a few changes, happy to be code reviewed.

fixes/modifications:

  1. fix: getHostByName is not coroutine safe.
  2. Write and send functions modified to write complete data before returning, can patch libraries working on blocking mode.
  3. utility function to get number of active coroutines get_co_routines_count(); set_timeouts(int fd , int read_timeout_secs, int write_timeout_secs); to change default timeout behavior.
  4. uses special errno = LIBCO_POLL_TIMEOUT to indicate the difference write/read returned with a timeout or an error.
  5. co_accept automatically prepares the fd coroutine capable.

Fork: https://github.com/abhinavabcd/libco
https://github.com/Tencent/libco/compare/master...abhinavabcd:master?diff=split&name=master

问题反馈

1.在未调用co_init_curr_thread_env函数前co_enable_hook_sys不能设置成功,而co_init_curr_thread_env的声明并未和co_enable_hook_sys的声明放在同一个头文件中,且co_enable_hook_sys没有返回值,不知道是否设置成功;
2.co_enable_hook_sys设置不成功时,调用socket函数未申请fd-info-buffer(alloc_by_fd函数),后面如果co_enable_hook_sys设置成功,但是是无法hook系统函数的(找不到fd-info-buffer,使用默认方法),这样对第三方库十分不友好(有些库必须需要在主线程初始化,且要使用默认函数),因此建议函数socket、fcntl、close不管什么时候都是hook的。

[New Feature] 多线程调度器支持

现在libco 是一种类lua coroutine 的单线程模式

有些场景用多线程模型还是更方便,考虑支持类 Go/Erlang 的调度器执行模型:

  • 一个IO线程完成epoll_wait 事件轮训、定时器
  • 一个线程池调度并行的执行 coroutine

这样引入一些带有block系统调用但耗时可控的第三方库也没啥问题

epoll_wait空转问题耗费CPU

void co_eventloop( stCoEpoll_t *ctx,pfn_co_eventloop_t pfn,void *arg )
在这个函数中调用epoll_wait定时1ms查询,进程请求量小时,会出现空转浪费CPU,可能达到1%到2%。
如果进程数量过多的话,这个CPU浪费也较明显。
建议这里考虑用户根据请求量来动态设置epoll_wait定时参数。

比如,可以根据void co_eventloop( stCoEpoll_t *ctx,pfn_co_eventloop_t pfn,void *arg )中的 pfn_co_eventloop_t pfn回调函数的返回值动态设置epoll_wait超时时间。

co_poll_inner函数有个异常情况需要处理

在AddTimeout出错的情况下,应该把前面已经加入到epoll的fd删除掉,否则这里返回了,而epoll调用回来的时候指针已经是野指针了。尽管AddTimeout出错的情况比较罕见,但是我确实是碰到了。

我发现co_hook_sys_call.cpp里面对connect的hook没有使用poll去等待异步结果,导致一些第三方库connect时失败

因为很多第三方库(比如hiredis或者mysql c driver) 创建连接时使用的是同步connect的函数, 接入libco后,mysql c driver 调用 mysql_real_connect 会返回115错误,调试发现当调用到hook的connect函数后,直接返回ret -1, errno为115。
我觉得这里是不是应该模拟同步connect时的ret, 即发起连接请求后立即调用co_poll yield出去,等待connect成功后再回到这里返回正确的ret。
现在的实现导致mysql c driver无法正确连接成功。

缺少文档

libco里文档太少了, 接口都不知道怎么用, 有没有详细的API文档呢?

关于调用栈的疑问

pCallStack的数组大小是128,意味着在不yield的情况下,resume 128次就会数组越界。
co_resume的时候,为什么不判断下iCallStackSize,对出现调用链过长的情况进行报错?

save_stack_buffer问题

void save_stack_buffer(stCoRoutine_t* occupy_co)
{
	///copy out
	stStackMem_t* stack_mem = occupy_co->stack_mem;
	int len = stack_mem->stack_bp - occupy_co->stack_sp;
        // 为什么直接就释放了,是否可以判断一下,如果当前的空间足够,则直接复用
	if (occupy_co->save_buffer)
	{
		free(occupy_co->save_buffer), occupy_co->save_buffer = NULL;
	}
	occupy_co->save_buffer = (char*)malloc(len); //malloc buf;
	occupy_co->save_size = len;
	memcpy(occupy_co->save_buffer, occupy_co->stack_sp, len);
}

如何在协程中添加锁的机制?

很多情况下需要对全局变量进行加锁操作,如果原有代码中使用到了pthread_mutex锁机制,这种情况下如何进行协程锁的替换呢?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.