记一次python的print函数引发的血案

我最近项目是基于自研的webserver框架实现的,支持C++、Java、Python等语言混合开发,上线一年多都没有发现重大问题。但就在昨天快下班时,运营同事突然打电话说生产环境无法导入报表,于是我登录生产环境,看了一下日志,发现报表文件已经正常上到服务器,但在解析时无法正常打开。

我们的报表都是xls格式,导入功能是Python开发的,我们选择xlrd库来解析xls文件。查看日志发现xlrd在打开文件时报“输入/输出错误”,具体日志如下:

[20190614 17:28:48|ERR] system error[Errno 5 输入/输出错误]

由于是线上问题,我马上转存日志后,就重启了webserver服务,重启后就可以正常导入报表,果然是万能的重启大法。

问题分析

但是问题还是要解决,由于是xlrd模块报错,一开始一直坚信xlrd库不会有问题,那么很有可能是xls文件损坏引起了,计算了一下报表文件的MD5值,发现文件是完整的,下载到本地可以正常打开,而且在线下环境导入竟然成功了,多次在线下测试都不能重现线上的问题。

排除了不是xls文件的问题后,开始怀疑是线上环境存在文件使用完后没有关闭的情况,导致长时间运行后webserver进程的文件句柄用完了,因此无法正常打开文件,于是用lsof -c webserver | wc -l命令跟踪了一个websever服务打开的文件数量,发现打开文件数量是稳定了,这就奇怪了,明明是“输入/输出”错误,难道xlrd库本身有问题,于是将webserver日志级别调到debug级别,等待线上问题重现后查看异常堆栈。

第二天上班时,线上就重新了这个问题,登录线上环境,查看日志发现异常调用堆栈如下:

[20190615 08:50:08|ERR] system error[Errno 5 输入/输出错误]
[20190615 08:50:08|ERR] system error[Traceback (most recent call last):
  File "/home/work/application/webserver/pyc/alibaba/importexlist.py", line 56, in main
    vec = GetDataList(app.www.yuechaoyule.com getPath(www.changjianggw.com) + url);
  File "/home/work/application/webserver/pyc/alibaba/importexlist.py", line 17, in GetDataList
    with xlrd.open_workbook(path) as xls:
  File "/usr/local/lib/python3.6/site-packages/xlrd/__init__.py"www.yuchengyule.com, line 157, in open_workbook
    ragged_rows=ragged_rows,
  File "/usr/local/lib/python3.6/site-packages/xlrd/book.py", line 88, in open_workbook_xls
    ragged_rows=ragged_rows,
  File "/usr/local/lib/python3.6/site-packages/xlrd/book.py", line 632, in biff2_8_load
    cd = compdoc.CompDoc(self.filestr,www.mingj2yl.com logfile=self.logfile)
  File "/usr/local/lib/python3.6/site-packages/xlrd/compdoc.py", line 119, in __init__
    % (len(mem), sec_size),www.rmutk.net file=logfile)
OSError: [Errno 5] 输入/输出错误

很明显,程序执行到xlrd/compdoc.py文件第119行出现异常,执行以下命令查看源码内容如下:

head -120 /usr/local/lib/python3.6/site-packages/xlrd/compdoc.py | cat -n
   114            mem_data_secs,www.yaoshiyulegw.com left_over = divmod(mem_data_len, sec_size)
   115            if left_over:
   116                #### raise CompDocError("Not a whole number of sectors")
   117                mem_data_secs += 1
   118                print("WARNING *** file size (%d) not 512 + multiple of sector size (%d)"
   119                    % (len(mem), sec_size), file=logfile)
   120            self.mem_data_secs = mem_data_secs # use for checking later

看了源码后有点不淡定了,心中有一万匹草泥马在奔腾,源码中只有print函数(第119行)与“输入/输出”有关,难道是print函数的问题,不可能呀,这个是Python的系统函数怎么可能会有问题?继续分析源码,发现print函数传入了file参数,但这个参数的值是sys.stdout

sys.stdout这不就是输出终端吗?分析至此,看来Python的print函数有猫腻,这时用lsof -c webserver命令重点观察了一下webserver打开文件情况:

webserver 10237 work  0u  CHR 136,0  0t0  3 /dev/pts/0(deleted)
webserver 10237 work  1u  CHR 136,0  0t0  3 /dev/pts/0(deleted)
webserver 10237 work  2u  CHR 136,0  0t0  3 /dev/pts/0(deleted)

对比线下测试环境的lsof -c webserver命令执行结果,发现上面打开文件记录后面多了一个deleted标记,说明对应的文件或设备已经被删除了。

webserver 10632 work  0u  CHR 136,0  0t0  3 /dev/pts/4
webserver 10632 work  1u  CHR 136,0  0t0  3 /dev/pts/4
webserver 10632 work  2u  CHR 136,0  0t0  3 /dev/pts/4

看来真的是线上webserver服务终端输出设备的问题,确认问题后立马重启线上webserver服务,再次执行lsof -c webserver命令输出如下:

webserver 10413 work  0u  CHR 136,0  0t0  3 /dev/pts/1
webserver 10413 work  1u  CHR 136,0  0t0  3 /dev/pts/1
webserver 10413 work  2u  CHR 136,0  0t0  3 /dev/pts/1

重启结果和预期一致,上面输出内容第4列0u/1u/2u实际上是webserver进程启动时最先打开的三个文件句柄,依次对应以下终端设备:

  • 0号文件---stdin标准输入设备
  • 1号文件---stdout标准输出设备
  • 2号文件---stderr错误输出设备

那么在什么情况下文件会出现deleted删除标记呢?原来webserver服务通过终端启动,当启动终端退出或关闭时,对应的终端设备也就不存在了,如果这时不关闭对应文件句柄,就会出现上面这种情况,不幸的是Python的print函数会把这种情况当异常处理,于是就产生了这个问题。

解决方案

问题原因找到了,怎么解决这个呢?既然文件的deleted标记是终端关闭时引起,那么一定是终端关闭时向内核发送了信号,然后内核根据这个信号将对应的文件句柄标记为deleted

实际上终端关闭时会向内核发送SIGHUP信号,我们可以调用signal函数给SIGHUP信号注册一个回调方法,然后在回调方法里面关闭终端相关的设备,就是对stdin/stdout/stderr执行close方法,相关代码如下:

//SIGHUP信号回调函数
void sighup(int signum)
{
    fclose(stdin);
    fclose(stdout);
    fclose(stderr);

    //忽略后继SIGHUP信号
    signal(signum, SIG_IGN);
}

//注册SIGHUP信号回调函数
signal(SIGHUP, sighup);

后续我会完善webserver架构的相关文档,然后将webserver框架发布到开源社区,到时欢迎广大猿友指正。

原文地址:https://www.cnblogs.com/qwangxiao/p/11072373.html

时间: 2024-08-24 14:03:47

记一次python的print函数引发的血案的相关文章

python 中 print 函数用法总结

Python 思想: “一切都是对象!” 在 Python 3 中接触的第一个很大的差异就是缩进是作为语法的一部分,这和C++等其他语言确实很不一样,所以要小心 ,其中python3和python2中print的用法有很多不同,python3中需要使用括号 缩进要使用4个空格(这不是必须的,但你最好这么做),缩进表示一个代码块的开始,非缩进表示一个代码的结束.没有明确的大括号.中括号.或者关键字.这意味着空白很重要,而且必须要是一致的.第一个没有缩进的行标记了代码块,意思是指函数,if 语句.

python 3 print函数用法总结

Python 3 print 函数用法总结 1. 输出字符串和数字 >>>print("runoob") # 输出字符串 runoob >>> print(100) # 输出数字 100 >>> str = 'runoob' >>> print(str) # 输出变量 runoob >>> L = [1,2,'a'] # 列表 >>> print(L) [1, 2, 'a'] &

一个Sqrt函数引发的血案

我们平时经常会有一些数据运算的操作,需要调用sqrt,exp,abs等函数,那么时候你有没有想过:这个些函数系统是如何实现的?就拿最常用的sqrt函数来说吧,系统怎么来实现这个经常调用的函数呢? 虽然有可能你平时没有想过这个问题,不过正所谓是"临阵磨枪,不快也光",你"眉头一皱,计上心来",这个不是太简单了嘛,用二分的方法,在一个区间中,每次拿中间数的平方来试验,如果大了,就再试左区间的中间数:如果小了,就再拿右区间的中间数来试.比如求sqrt(16)的结果,你先试

【转载】一个Sqrt函数引发的血案

转自:http://www.cnblogs.com/pkuoliver/archive/2010/10/06/sotry-about-sqrt.html 源码下载地址:http://diducoder.com/sotry-about-sqrt.html 好吧,我承认我标题党了,不过既然你来了,就认真看下去吧,保证你有收获. 我们平时经常会有一些数据运算的操作,需要调用sqrt,exp,abs等函数,那么时候你有没有想过:这个些函数系统是如何实现的?就拿最常用的sqrt函数来说吧,系统怎么来实现这

python中print()函数的“,”与java中System.out.print()函数中的“+”

python中的print()函数和java中的System.out.print()函数都有着打印字符串的功能. python中: print("hello,world!") 输出结果为:hello,world! java中: System.out.print("hello,world!"); 输出结果为:hello,world! 我们可以看到,这两个函数的用法是一样的 print()函数还有这种用法: print("1+1=",1+1) 输出结

Python之print函数详解

输出的 print 函数总结: 1. 字符串和数值类型可以直接输出 [python] view plain copy >>> print(1) 1 >>> print("Hello World") Hello World 2.变量无论什么类型,数值,布尔,列表,字典...都可以直接输出 [python] view plain copy >>> x = 12 >>> print(x) 12 >>> 

Python中print函数输出时的左右对齐问题

为了将print函数输出的内容对齐,笔者在http://www.jb51.net/article/55768.htm中找到了左右对齐的方法.整理如下: 一.数值类型(int.float) #  %d.%f是占位符>>> a = 3.1415926>>> print("%d"%a)    #%d只能输出整数,int类 3>>> print("%f"%a) #%f输出浮点数 3.141593>>>

python的print函数自动换行及其避免

print函数自带换行功能,即在输出内容后会自动换行,但是有时我们并不需要这个功能,那怎么办呢?这时候就需要用到end这个参数了,使用方法参考下面这段打印$矩阵的代码: i = 1 while i<=5: j = 1 while j<=5: print("$ ",end="") j = j + 1 print("") i = i + 1 运行结果: 原文地址:https://www.cnblogs.com/shujuxiong/p/9

Python中print函数中中逗号和加号的区别

先看看print中逗号和加号分别打印出来的效果.. 这里以Python3为例 1 print("hello" + "world") helloworld 1 print("hello", "world") hello world 这里发现加号的作用是连接字符串 而逗号相当于用空格连接字符串. 尝试一下不同数据类型的操作.. 1 print("hello" + 123) TypeError: must be