nohup启动的Python脚本在time.sleep()处挂起的原因排查
time.sleep()处挂起的可能原因 根据你描述的情况——脚本运行两天后卡在time.sleep(60),日志最后停留在DEBUG Starting Sleep,且进程未终止,已经排除内存泄漏、I/O挂起和连接超时,下面是几个可能的原因及排查方向:
1. 系统信号导致进程被暂停
如果系统向进程发送了SIGSTOP信号(比如误操作执行kill -STOP <pid>,或是某些监控工具的自动行为),进程会进入暂停状态,既不会继续执行代码,也不会终止,看起来就像是"挂起"了。
- 排查方法:
- 用
ps -o stat <pid>查看进程状态,若状态为T(stopped),说明进程确实被暂停。 - 尝试发送
SIGCONT信号(kill -CONT <pid>),观察进程是否恢复运行。 - 检查系统日志(如
/var/log/syslog或/var/log/messages),排查是否有相关信号的发送记录。
- 用
2. 系统时钟回拨导致sleep时长异常
Python的time.sleep()依赖系统时钟计算等待时长,如果系统时钟被手动回拨、或是NTP服务同步时向后调整了大量时间,sleep的实际等待时间会被异常延长。比如调用time.sleep(60)后,时钟回拨了2天,进程会实际等待2天+60秒,看起来就像是挂起。
- 排查方法:
- 查看系统时钟同步记录(如
timedatectl status查看NTP状态,或检查/var/log/ntp.log),确认是否有大规模时钟回拨操作。 - 在脚本中添加sleep前后的时间戳日志,方便后续定位:
logging.debug(f"Starting sleep at {datetime.datetime.now()}") time.sleep(60) logging.debug(f"Finished sleep at {datetime.datetime.now()}")
- 查看系统时钟同步记录(如
3. 未捕获的BaseException导致执行中断
你的脚本中except Exception仅能捕获继承自Exception的异常,但KeyboardInterrupt、SystemExit等属于BaseException层级,不会被捕获。如果进程收到SIGINT等信号,可能在time.sleep()处触发未捕获的异常,导致进程停止执行但未终止(这种情况多与第三方库修改信号处理逻辑有关)。
- 排查方法:
- 扩大异常捕获范围,改为捕获
BaseException,看是否能捕获到相关异常:try: logging.debug("Started loop.") do_some_stuff() logging.debug("Stuff was done.") except BaseException as e: logging.exception("Unhandled exception occurred") - 检查
do_some_stuff()中是否有修改信号处理的代码(如signal.signal()调用)。
- 扩大异常捕获范围,改为捕获
4. 系统资源限制触发隐性限制
虽然你排查了内存泄漏,但仍可能存在其他资源限制:
文件描述符耗尽:如果
do_some_stuff()中频繁打开文件却未关闭,可能导致进程打开的文件描述符达到系统上限(ulimit -n),虽然sleep本身不依赖文件描述符,但可能影响后续日志写入,让你误以为卡在sleep阶段。CPU时间限制:若系统设置了进程CPU时间上限(
ulimit -t),当进程累计CPU时间达标时,系统会发送SIGXCPU信号,若进程忽略该信号,可能进入异常暂停状态。排查方法:
- 用
ulimit -a查看当前资源限制,确认是否有严格的阈值。 - 用
lsof -p <pid>查看进程打开的文件描述符数量,判断是否接近上限。 - 检查系统日志中是否有资源限制相关的警告信息。
- 用
5. 第三方库的隐性异常
如果do_some_stuff()依赖第三方库,某些库可能存在线程死锁、资源未正确释放的问题——虽然表面上do_some_stuff()执行完成,但库内部的异常状态可能影响后续的time.sleep()正常执行。
- 排查方法:
- 逐步简化
do_some_stuff()的逻辑,注释掉部分功能后运行脚本,定位是否由某段代码引发问题。 - 检查第三方库的版本,查看是否有已知的类似bug记录。
- 逐步简化
内容的提问来源于stack exchange,提问作者w1kl4s

