单线程压测脚本触发多线程bug
背景
在一次和银行联调中,银行提供了一个加密动态库,加密库使用在我们的服务端,在对服务端单线程压测中,触发了加密动态库的多线程bug.
发现过程
压测脚本如下:用 bash test.sh运行
while [ true ]
do
python server_test.py
python server_test.py
done
其中 server_test.py是开发环境用来测试服务端的脚本,主要功能是连接服务端,构造请求包,等待服务端的响应包。
这里整体的执行流程是这样:
1:python server_test.py运行后,会请求到服务端,服务端会给银行发送对应的请求报文, 当银行回了响应报文之后,server_test.py脚本才会退出。
2:服务端本身是多线程的,所以一个线程来处理请求报文就OK了,处理完回到了线程池,下一次请求继续使用一个线程来处理请求。 所以所有的请求都是串行发送给我们的服务的,所有的响应也是串行被服务端处理的 。那么这里始终进行的是单线程压测(这里有两层含义,shell脚本始终只有一个进程在发包,服务端进程始终只需要拿出一个线程过来处理)
问题出现在,当我想结束这个测试脚本时,不停的按 ctrl+c ,此时shell脚本并没有退出,只是可以看到python脚本中有打印如下字样
可以看到python脚本中捕获了ctrl+c触发的sigint信号,python脚本退出了,但是shell脚本中并没有响应的动作,重复多次,看到的结果是相同的,继续重复多次后,会突然发现前置机(即服务端)进程coredump 了。
解释:
先解释前置机进程coredump。当按下ctrl + c 的前后,有如下事件:
1:python 进程是bash的一个子进程。bash中fork了一个子进程,在子进程中exec python进程。
2:python进程发包---->前置机解析处理---->前置机回包响应---->python进程退出---->新的python进程被fork exec出来,一直重复下去。
3: 当按下ctrl + C 触发了sigint后,信号被传递给子进程,即python进程,python脚本中没有设置信号处理函数,所以默认处理就是退出python进程。
4:python 脚本退出前,可能请求报文已被构造好发送给了前置机,前置机则正在处理报文(因为构造发包是很快的,前置机还会将请求转到银行,那么python脚本中大部分时间都在等待前置机回响应包,按下ctrl + c 后大概率python脚本已经发包,然后在等待前置机响应)
5:shell脚本并没有结束(之后会解释),那么shell脚本继续运行,fork了python子进程继续发包,前置机进程此时正在处理上一个python进程的请求报文,又收到了一个请求报文,变成多线程处理了。重复多次,触发了加密库的多线程bug。
这里需要解释的点还有两个:
1:多进程中信号究竟是传递给父进程还是子进程还是父子进程都会收到
2:为什么子进程退出了(即python进程)而父进程(bash)没有任何响应。
回答:
1:sigint信号会传递给整个前台进程组,父子进程都会收到。
2:参考https://stackoverflow.com/questions/6108953/how-does-ctrl-c-terminate-a-child-process, shell进程有特殊设置,有默认sigint处理函数(不是默认退出的函数,也可在脚本中自定义信号处理函数),而不会退出进程。