一个用libcurl多线程下载断言错误问题的排查

某数据下载程序,相同版本的代码,在64位系统中运行正常,但在32位系统中概率性出现断言错误。一旦出现,程序无法正常继续,即使重启亦不行。从年前会上领导提出要追到根,跟到底,到年后的今天,经过排查、自测、试点,算是告一段落了。文中没有很难的技术问题,但过程还是值得记录的。本文从后来者角度总结一下解决问题的过程,同时给出相关测试代码。

由于本文没有技术含量,请谨慎按需阅读。

起因

当运维人员把出错的截图发给我时,我回想起1年半前的那个夏天,那天下午,运维人员将同一个错误截图给我,后来回退版本了,再也没出现了。这次,运维人员上报给了领导。会上我也答不出来为什么在32位系统上会报错,而在64位系统中却不行,也回答不了为什么回退版本又可以。下面这个错误,在笔记里躺了很久,现在又要翻出来,一点也没变化:

ath.c:193: _gcry_ath_mutex_lock: Assertion `*lock == ((ath_mutex_t) 0)' failed.

这是某个动态库报的断言错误,不是业务程序直接提示的。虚拟机模拟不出问题,现场机器没有gdb,也无法生成coredump,也没有pstrace,只能靠头脑分析排查了。

排查及解决

动态库定位

报错信息关键信息为ath.c_gcry_ath_mutex_lockath_mutex_t

经搜索,得到了一些有用的信息。

在libssh2官方网站上,找到一篇关于FIPS兼容性的帖子FIPS Compliance,提问者的错误是用sftp通过libcurl下载文件时产生的。路线和所遇问题几乎一样,出现断言错误的库为libgcrypt。在stackoverflow网站上找到这个帖子,帖子回答者提到:

Obviously you are using libgcrypt in there, either directly or through some library (liboauth?). Multithreaded use of gcrypt requires initialization, as documented at gnupg.org/documentation/manuals/gcrypt/Multi_002dThreading.html - either you forgot it, or one of the maintainers of libraries you used did. Check the documentation… –
DevSolar
Dec 13, 2011 at 17:00
The problem was that more than one thread tries to get a http request at the same time. That’s not possible. So I used mutexes to avoid that.

使用libgcrypt时,在多线程中要初始化,至于初始化什么,怎么初始化,谁初始化,由于涉及libcurl->libssh2->libgcrypt等库,路径较深,鞭长莫及,代码人一声叹气。

关于ath.c断言语句的跟踪记录

前面定位到了libgcrypt库,在32位系统上用strings命令查找出错关键字:

 strings /lib/i386-linux-gnu/libgcrypt.so.11.7.0 | grep "*lock =="
*lock == ((ath_mutex_t) 0)
*lock == ((ath_mutex_t) 1)strings /lib/i386-linux-gnu/libgcrypt.so.11.7.0 | grep "_gcry_ath_mutex_lock"
_gcry_ath_mutex_lock

是这个库无疑了。但11.7.0版本找不到ath.c文件。再在64位系统上查:

 strings /lib64/libgcrypt.so.11.8.2 | grep "*lock =="
*lock == ((ath_mutex_t) 0)
*lock == ((ath_mutex_t) 1)strings /lib64/libgcrypt.so.11.8.2 | grep "_gcry_ath_mutex_lock"
_gcry_ath_mutex_lockfind /usr/ -name "libgcrypt*"
/usr/lib64/libgcrypt.so.11.8.2
/usr/lib64/libgcrypt.so.11
/usr/share/doc/libgcrypt-1.5.3

从信息中猜测,可能的版本是1.5.3。下载该版本解压,得到ath.c关键语句:
在这里插入图片描述

第193行,正是这多天魂牵梦萦想看到的语句。

但是,这只是知道了出错的地方而已,还不知道如何出错。

问题定位

从错误信息上看,和锁有关,进而推断和多线程有关。分析业务代码,的确有多线程下载。起初,跟踪线程内部的curl变量,但没有出现越界使用情况,都在相同内部线程完成了,每次下载,都用curl_easy_init初始化,最后用curl_easy_cleanup清理,通过打印跟踪可以确认这一点。

回到业务程序上。既然是多线程下载出错,就将多线程改成单线程,或者加上互斥锁,不让他们同时运行。这份代码比大锤的年龄还大,有一定的历史沉淀,且有较多名称相近的函数,类似于北湖北路,北湖南路,北湖东路等,不好改动。于是加上锁,再测试,没有发现问题。

解决方法

但依然没有找到原因。

访问libcurl官方示例页面,找到多线程例子,但参考价值不大,于是搭建sftp服务器,写了测试程序。经摸索,发现在一线程下载过程的同时另一线程也下载,则必然出错。开始时,下载的文件小,很快下载完毕,下载大文件时问题即刻暴露了。

回查业务代码,在两线程启动时,人工加了10秒的延时,当数据量较多大了,前一线程未下载完毕,后一线程启动,因此报错了。之前相安无事,应该是两线程下载的数据量并非都大,前一线程较快完成了下载。

但无论如何,在两线程之间加锁,的确能解决问题。

反馈

在试点跑了3天,暂时没有收到问题反馈。应该大概的确解决了这个问题。

小结

至于为何libcurl无法多线程下载,为何偏偏在32位系统上出现,其实还没有找到根本问题所在。多方测试,64位系统的确未发现有。于是在业务代码中通过宏定义限定只在32位系统才加锁。

附:自测程序

源码:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <curl/curl.h>#define DOWNLOAD_LOCKstatic pthread_mutex_t connlock;void init_locks(void)
{pthread_mutex_init(&connlock, NULL);
}void kill_locks(void)
{pthread_mutex_destroy(&connlock);
}void my_locks(void)
{
#ifdef DOWNLOAD_LOCKpthread_mutex_lock(&connlock);
#endif
}    void my_unlocks(void)
{
#ifdef DOWNLOAD_LOCKpthread_mutex_unlock(&connlock);
#endif
}//lldebug LoginStr aftp:123456 RemoteFile: sftp://192.168.168.88/DataStorary/Server/MyData/BigFile.dat LocalFile: /tmp/data/download/BigFile.datstatic size_t my_write(void *buffer, size_t size, size_t nmemb, void *stream)
{/* not interested in the downloaded bytes, return the size */ (void)buffer;  /* unused */ (void)stream; /* unused */ return (size_t)(size * nmemb);
}void downloadFile(CURL *curl, const char *LoginStr, const char * RemoteFile, const char *LocalFile)
{//CURL *curl = InitCurl();if(curl == NULL){return;}curl_easy_setopt(curl, CURLOPT_USERPWD, LoginStr);curl_easy_setopt(curl, CURLOPT_URL, RemoteFile);curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, my_write);printf("lldebug %s().%d before curl_easy_perform ptr: %p\n", __func__, __LINE__, curl);CURLcode code = curl_easy_perform(curl);if(CURLE_OK != code){printf("curl_easy_perform failed, %d: %s\n", code, curl_easy_strerror(code));}
}static void *download_1(void *url)
{CURL *curl;const char* loginStr = "aftp:123456";//const char* rfile = "sftp://192.168.168.88/DataStorary/Server/MyData/smallfile.txt";const char* rfile = "sftp://192.168.168.88/DataStorary/Server/MyData/BigFile.dat";const char* lfile = "/tmp/file_1.txt";my_locks();printf("lldebug %s().%d begin++++++++++++++++++\n", __func__, __LINE__);curl = curl_easy_init();printf("lldebug %s().%d >>>>>>>> init curl ptr: %p\n", __func__, __LINE__, curl);downloadFile(curl, loginStr, rfile, lfile);printf("lldebug %s().%d <<<<<<<< download done curl ptr: %p\n", __func__, __LINE__, curl);curl_easy_cleanup(curl);printf("lldebug %s().%d end++++++++++++++++++\n", __func__, __LINE__);my_unlocks();
}static void *download_2(void *url)
{CURL *curl;const char* loginStr = "aftp:123456";const char* rfile = "sftp://192.168.168.88/DataStorary/Server/MyData/smallfile.txt";const char* lfile = "/tmp/file_2.txt";my_locks();printf("lldebug %s().%d begin------------------\n", __func__, __LINE__);curl = curl_easy_init();printf("lldebug %s().%d >>>>>>>> init curl ptr: %p\n", __func__, __LINE__, curl);downloadFile(curl, loginStr, rfile, lfile);printf("lldebug %s().%d <<<<<<<< download done curl ptr: %p\n", __func__, __LINE__, curl);curl_easy_cleanup(curl);printf("lldebug %s().%d end------------------\n", __func__, __LINE__);my_unlocks();
}int main(int argc, char **argv)
{pthread_t tid1;pthread_t tid2;init_locks();curl_global_init(CURL_GLOBAL_ALL);pthread_create(&tid1, NULL, download_1, NULL);usleep(1000);pthread_create(&tid2, NULL, download_2, NULL);pthread_join(tid1, NULL);pthread_join(tid2, NULL);kill_locks();curl_global_cleanup();return 0;
}

编译:

g++ test_thread.cpp  -I/usr/local/curl/include -I/usr/include -lcurl

加锁情况下运行结果:

lldebug download_1().78 begin++++++++++++++++++
lldebug download_1().82 >>>>>>>> init curl ptr: 0x9216340
lldebug downloadFile().57 before curl_easy_perform ptr: 0x9216340
lldebug download_1().86 <<<<<<<< download done curl ptr: 0x9216340
lldebug download_1().91 end++++++++++++++++++
lldebug download_2().105 begin------------------
lldebug download_2().110 >>>>>>>> init curl ptr: 0x9216340
lldebug downloadFile().57 before curl_easy_perform ptr: 0x9216340
lldebug download_2().114 <<<<<<<< download done curl ptr: 0x9216340
lldebug download_2().118 end------------------

不加锁情况下运行结果:

lldebug download_1().78 begin++++++++++++++++++
lldebug download_1().82 >>>>>>>> init curl ptr: 0x9d37340
lldebug downloadFile().57 before curl_easy_perform ptr: 0x9d37340
lldebug download_2().105 begin------------------
lldebug download_2().110 >>>>>>>> init curl ptr: 0x9d4de18
lldebug downloadFile().57 before curl_easy_perform ptr: 0x9d4de18
a.out: ath.c:193: _gcry_ath_mutex_lock: Assertion `*lock == ((ath_mutex_t) 0)' failed.
Aborted

用gdb调试过程:

(gdb) r
Starting program: /home/latelee/libcurl_test/a.out 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/i686/cmov/libthread_db.so.1".
[New Thread 0xb769eb70 (LWP 7680)]
lldebug download_1().101 begin++++++++++++++++++
lldebug download_1().105 >>>>>>>> init curl ptr: 0x805a340
lldebug downloadFile().80 before curl_easy_perform ptr: 0x805a340
[New Thread 0xb6e9db70 (LWP 7681)]
lldebug download_2().128 begin------------------
lldebug download_2().133 >>>>>>>> init curl ptr: 0x8070e18
lldebug downloadFile().80 before curl_easy_perform ptr: 0x8070e18
a.out: ath.c:193: _gcry_ath_mutex_lock: Assertion `*lock == ((ath_mutex_t) 0)' failed.Program received signal SIGABRT, Aborted.
[Switching to Thread 0xb6e9db70 (LWP 7681)]
0xb7fe1424 in __kernel_vsyscall ()
(gdb) bt
#0  0xb7fe1424 in __kernel_vsyscall ()
#1  0xb7cf5941 in *__GI_raise (sig=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#2  0xb7cf8d72 in *__GI_abort () at abort.c:92
#3  0xb7ceeb58 in *__GI___assert_fail (assertion=0xb7938626 "*lock == ((ath_mutex_t) 0)", file=0xb7938620 "ath.c", line=193, function=0xb7938674 "_gcry_ath_mutex_lock") at assert.c:81
#4  0xb78e9605 in ?? () from /lib/i386-linux-gnu/libgcrypt.so.11
#5  0xb792b02d in ?? () from /lib/i386-linux-gnu/libgcrypt.so.11
#6  0xb792c889 in ?? () from /lib/i386-linux-gnu/libgcrypt.so.11
#7  0xb792ae7d in ?? () from /lib/i386-linux-gnu/libgcrypt.so.11
#8  0xb78f82d9 in ?? () from /lib/i386-linux-gnu/libgcrypt.so.11
#9  0xb78f8a14 in ?? () from /lib/i386-linux-gnu/libgcrypt.so.11
#10 0xb78e078c in gcry_md_open () from /lib/i386-linux-gnu/libgcrypt.so.11
#11 0xb7c603b6 in ?? () from /usr/lib/i386-linux-gnu/libssh2.so.1
#12 0xb7c6f154 in ?? () from /usr/lib/i386-linux-gnu/libssh2.so.1
#13 0xb7c5a2c8 in ?? () from /usr/lib/i386-linux-gnu/libssh2.so.1
#14 0xb7c67108 in ?? () from /usr/lib/i386-linux-gnu/libssh2.so.1
#15 0xb7c67489 in ?? () from /usr/lib/i386-linux-gnu/libssh2.so.1
#16 0xb7c680db in libssh2_sftp_init () from /usr/lib/i386-linux-gnu/libssh2.so.1
#17 0xb7fa0688 in ?? () from /usr/lib/i386-linux-gnu/libcurl.so.4
#18 0xb7fa3f63 in ?? () from /usr/lib/i386-linux-gnu/libcurl.so.4
#19 0xb7fa4595 in ?? () from /usr/lib/i386-linux-gnu/libcurl.so.4
#20 0xb7f80380 in ?? () from /usr/lib/i386-linux-gnu/libcurl.so.4
#21 0xb7f8067a in ?? () from /usr/lib/i386-linux-gnu/libcurl.so.4
#22 0xb7f80732 in ?? () from /usr/lib/i386-linux-gnu/libcurl.so.4
#23 0xb7f8d255 in ?? () from /usr/lib/i386-linux-gnu/libcurl.so.4
#24 0xb7f8e033 in curl_easy_perform () from /usr/lib/i386-linux-gnu/libcurl.so.4
#25 0x08048acf in downloadFile(void*, char const*, char const*, char const*) ()
#26 0x08048c60 in download_2(void*) ()
#27 0xb7cb7c39 in start_thread (arg=0xb6e9db70) at pthread_create.c:304
#28 0xb7da1d4e in clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:130
(gdb) 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/527041.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据标注专业团队

数据标注服务 在跟一些淘宝、多多商家老板合作后&#xff0c;客户一般付款后&#xff0c;中介是有20%左右的提成&#xff0c;我们主要是希望可以实现数据标注无中介化&#xff0c;有需求可以直接联系数据标注团队直接负责人&#xff0c; 若有意愿请添加添加v&#xff1a;shu_ju…

全网最最最详细-bash: wget: 未找到命令如何解决教程

在基于 Red Hat 的系统中&#xff0c;包管理器是 yum 或者在更新的版本中是 dnf。 如果你的系统是 CentOS 7 或更早版本&#xff0c;你应该使用 yum 命令来安装 wget&#xff1a; sudo yum install wget如果你的系统是 CentOS 8 或者 Fedora&#xff0c;你可能需要使用 dnf 命…

SPI读取TLE5012B编码器角度数据_ACM32G103

买到一个带编码器的电机&#xff0c;卖家留出来读取编码器数据的接口有SPI 具体的原理图如下&#xff1a; P2标注的是SPI的接口&#xff0c;其中MISO和MOSI是硬件连接到一起的 使用ACM32G103的硬件SPI2去读取其角度数据 原理大概如下&#xff1a; 1、先发送读取数据的指令&…

Java二叉树 (2)

&#x1f435;本篇文章将对二叉树的一些基础操作进行梳理和讲解 一、操作简述 int size(Node root); // 获取树中节点的个数int getLeafNodeCount(Node root); // 获取叶子节点的个数int getKLevelNodeCount(Node root,int k); // 获取第K层节点的个数int getHeight(Node r…

浅谈2024 年 AI 辅助研发趋势!

目录 ​编辑 引言 一、AI辅助研发现状 1. 技术发展 2. 工具集成 3. 应用场景 二、AI辅助研发趋势 1. 更高的自动化程度 2. 更高的智能化程度 3. 更多的领域应用 4. 更高的重视度 三、结论 四. 完结散花 悟已往之不谏&#xff0c;知来者犹可追 创作不易&#xff…

阿里云服务器多少钱一年?价格表新鲜出炉(2024)

2024阿里云服务器优惠活动政策整理&#xff0c;阿里云99计划ECS云服务器2核2G3M带宽99元一年、2核4G5M优惠价格199元一年&#xff0c;轻量应用服务器2核2G3M服务器61元一年、2核4G4M带宽165元1年&#xff0c;云服务器4核16G10M带宽26元1个月、149元半年&#xff0c;云服务器8核…

SpringCloudGateway理论与实践

文章目录 网关介绍为什么需要网关Gateway 使用gateway pom依赖yml 配置重启测试总结 断言过滤器工厂路由过滤器的种类请求头过滤器默认过滤器全局过滤器总结 Gateway解决跨域 网关介绍 Spring Cloud Gateway 是一个基于Spring Framework 5&#xff0c;由Spring Cloud团队开发的…

如何解决新版的anaconda notebook 打不开浏览器

1 安装nodejs 先安装nodejs&#xff0c;里面有很多需要用node来启动服务 2 一片空白 安装jupyter以后启动&#xff0c; 结果就得到了如下&#xff0c;在chrome里面打开以后&#xff0c;一片空白 3 列出环境 conda create --name pytorch python3.9 conda env list cond…

Kubernetes-4

文章目录 Kubernetes-41、pod的生命周期2、pod的中止过程3、强制终止pod4、查看资源类型4.1、kubectl get 后面接的都是资源类型4.2、kubectl api-resources 查看目前有哪些资源类型 5、容器的状态5.1、总结5.2、Pod 状态和 Pod 内部容器状态5.3、容器的重启策略 6、探针probe6…

C/C++指针详解

接下来我们来介绍一下什么是指针&#xff1f; 指针其实就是元素存放地址&#xff0c;更加形象的比喻&#xff1a;在酒店中如果你想要去注必须去付费不然不能住&#xff0c;在计算机也同样如此&#xff08;但是不需要付费哦&#xff09;每当我们使用一个变量或其他需要申请空间…

C/C++编程-理论学习-通信协议理论

通信协议理论 protobuf简述使用简介proto 文件为了nanopb 编译.proto文件修改生成器行为 streamsoutput streamsinput streams Data types(数据类型)Field callbacks(字段回调)Encoding callbacks(编码回调)Message descriptor(信息描述)三个关键字required、optional、repeate…

每日OJ题_路径dp②_力扣63. 不同路径 II

目录 力扣63. 不同路径 II 解析代码 力扣63. 不同路径 II 63. 不同路径 II 难度 中等 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;…