修复libcurl域名解析超时引起的内存越界问题

程序发布后在一个用户的机器上频繁出现崩溃,最终定位到崩溃来自一个断言失败:

assert(pthread_self() != main_thread_id);

上面这条语句出现在工作线程回调的函数中,竟然发生了工作线程ID和主线程ID相同的怪事,观察了运行日志,发现使用libcurl发起HTTP请求如果超时则有很大机率会断言失败导致崩溃,在使用libcurl发起HTTP请求的代码块前后输出工作线程ID,工作线程ID出现了变化,根据经验很可能是出现了内存越界。

最终找到了几篇 libcurl 多线程安全相关的文章:

修复步骤总结如下:

  • 在主线程起始处初始化 libcurl

    curl_global_init(CURL_GLOBAL_ALL);
    
  • 禁止 libcurl 通过 alarm 实现域名解析超时

    curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
    

    如果不做下面的最后一步, libcurl 上设置的超时都会无效。

  • 编译 libcurl 时启用 c-aresthreaded resolver ,以支持域名解析超时

    ./configure --enable-ares
    

    ./configure --enable-threaded-resolver
    

    Asynch resolving in libcurl》对 c-aresthreaded resolver 两种方式进行了比较,简而言之:

    • c-ares 是一个异步的域名解析库,开销更少,但是它并非使用系统原生的方式实现,对于定制系统(如:hosts或resolv.conf不在标准位置)可能会有问题。
    • threaded resolver 每次域名解析都会开一个线程,解析完成后销毁线程,开销会大一些,但是稳定性、兼容性更好。

按照上面的步骤启用 c-ares 进行修改后程序运行了一整天,没有再崩溃。