Python进程陷入死循环之后怎么办?这种情况下单纯分析代码很难快速定位到死循环发生的位置。如果能获取到进程当前的调用栈,那么问题就容易解决了。

本地处理策略

如果是在本地开发环境遇到这个问题,那么最为简便的方式就是使用PyCharm的Attach to Local Process这一功能。

在PyCharm菜单栏里Run/Attach to Local Process,然后选择本地Python进程。连接上之后暂停进程,PyCharm里面就会直接定位到最后在执行中的语句。于是就很方便的知道问题的出处了。

服务端处理策略

gdb

可惜,通常遇到这种问题都不是在本地。服务端遇上这个问题的话,相对就麻烦不少。DebuggingWithGdb上介绍了使用gdb来调试的方法,

安装必要的依赖之后,

apt-get install gdb python2.7-dbg

可以用gdb attach上进程,之后再用py-系列命令去获取信息与调试。之前某次遇到问题时,尝试使用此方法未能成功,不过好像又可以了,估计是当时环境没弄正确..

除了gdb之外,还有没其它方法?

signal

Python的sys模块中_current_frames可以获取到当前各个线程的frame,通过frame可以获取到thread当前调用栈,

def output_stacks():
  for tid, frame in sys._current_frames().iteritems():
    print 'thread: ', tid
    print ''.join(traceback.format_list(traceback.extract_stack(frame)))
    print inspect.getargvalues(frame)

因此只要有机会能够执行这个方法进行输出,也就能定位到死循环发生的位置。

这种需求下可以考虑使用signal,通过定义signal处理函数来让进程按既定方式输出内容,

import signal

def signal_handler(signum, frame):
    output_stacks()


signal.signal(signal.SIGUSR1, signal_handler)

在命令行下执行,

kill -30 pid

来给进程发送指定信号,对应进程就可以接收该信号并进行处理。

RPyC

上面两种方式需要登录到服务器上进行处理,如果在本地能远程获取到如上信息就更为方便。在这个思路下,服务端在启动的时候就需要提供能够远程访问的服务。RPyC是其中一种方式。服务端可以启动一个线程用于监听RPyC服务,

import threading

from rpyc.core import SlaveService
from rpyc.utils.server import ThreadedServer


class RPyCThread(threading.Thread):

    def __init__(self, port):
        super(RPyCThread, self).__init__()
        self.port = port

    def run(self):
        server = ThreadedServer(SlaveService, port=self.port, reuse_addr=True)
        server.start()

在本地通过RPyC进行连接并调用相应函数来获取输出,

修改output_stacks让其不直接输出,而是将结果返回,

def output_stacks():
  result = []
  for tid, frame in sys._current_frames().iteritems():
    result.append(
      (tid, ''.join(traceback.format_list(traceback.extract_stack(frame))), inspect.getargvalues(frame))
    )
  return result

在本地按照之前记录过的方式进行处理,

connect(ip, port)
from xxx import output_stacks
print output_stacks()

如此就能快速在本地获取到服务端进程中的调用栈了。