一个ruby多线程无限挂起的问题

在写一个ruby多线程程序的时候遇到了一个非常奇怪的一个问题: 程序的部分线程在工作一段时间后进入无限等待, 不再工作, 由于主线程需要等待各个子线程任务的结束, 因此导致整个程序被锁住.

查了半天, 发现问题可能是出在getaddrinfo这个函数上. 好吧, 又被多线程好好上了一课;) 这里简单记录一下整个过程.

问题发现

这个ruby多线程程序主要是为了并发抓取一些页面来进行分析. http-client选择了rest_client. 基本的代码如下:

strange.rb
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
require 'thread'
require 'rest_client'
require 'addressable/uri'

thrs = []

queryer = lambda do |tq, rq|
    while 1
        url = tq.pop

        begin
            RestClient::Request.execute(
                method: :get,
                url: Addressable::URI.parse(url).normalize.to_str
            ) do |response, request, result, &block|
                ## do some work ##
                ## hello world ##
            end
        rescue => e
            ## write some logs
        end
    end
end

task_queue = Queue.new
result_queue = Queue.new

10.times do |i|
    thrs << Thread.new(task_queue, result_queue, &queryer)
end

while 1
    ## push some url to task_queue

    ## check task_queue.num_waiting
    ## get result from result_queue
end

在实际运行中, 会发现整个进程被阻塞主, 所有线程都不再工作.

排查

发现程序没有正常运行, 我的第一反应就是是否是主线程被锁了. 但经过简单的print调试后发现主线程仍在正常工作. 接着尝试打印了一下每个线程的状态(thr.status), 发现都是在sleep状态. 这个现象貌似有点像是单个线程在执行一个任务时被锁住的样子. 之后又strace了下各个线程的系统调用, 发现了有些线程的系统调用有点诡异:

strace info
1
2
3
4
5
6
7
8
9
10
--- SIGVTALRM (Virtual timer expired) @ 0 (0) ---
rt_sigreturn(0x1a) = -1 EINTR (Interrupted system call)
recvmsg(18, 0x7f7e62407500, 0) = ? ERESTARTSYS (To be restarted)
--- SIGVTALRM (Virtual timer expired) @ 0 (0) ---
rt_sigreturn(0x1a) = -1 EINTR (Interrupted system call)
recvmsg(18, 0x7f7e62407500, 0) = ? ERESTARTSYS (To be restarted)
--- SIGVTALRM (Virtual timer expired) @ 0 (0) ---
rt_sigreturn(0x1a) = -1 EINTR (Interrupted system call)
recvmsg(18, 0x7f7e62407500, 0) = ? ERESTARTSYS (To be restarted)
--- SIGVTALRM (Virtual timer expired) @ 0 (0) ---

而正常的是:

strace info
1
futex(0x160f664, FUTEX_WAIT_PRIVATE, 61, NULL

根据上面的错误信息google了一下, 发现了一个很长的帖子(看完还是蛮有收获的). 里面有位同学多线程用到了pipe, 也出现了类似的情况. 回过头来看看输出的日志, 着重看了下是否有抛异常什么的. 发现了类似下面的信息:

log info
1
2
3
Broken pipe - SSL_connect

getaddrinfo: Name or service not known

难道我这里也是由于pipe导致的? 先尝试把https请求排除, 但是运行后仍然会出现类似的情况. 默认问题可能是出在getaddrinfo上面? google一下后发现了也有同学有相同的问题. 状况基本相同. 另外stackoverflow也有一个有关这个函数的问题. 看起来这个函数是非线程安全的? 这让我想起了libcurl多线程的dns查询问题.

解决

通过浏览上面的情况, 先使用Resolv来进行dns查询, 然后直接发起http请求. 发现通过这种方式可以避免上述问题的产生.

Comments