BrokenPipeError错误和subprocess.run()超时参数在Windows上无效
1、问题的发现
今天,一个在windows上运行良好的python脚本放到linux下报错,提示错误 BrokenPipeError: [Errno 32]Broken pipe。经调查是subprocess.run方法的timeout参数在linux上的表现和windows上不一致导致的。
try:
ret = subprocess.run(cmd, shell=True, check=True, timeout=5,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except Exception as e:
logging.debug(f"Runner FAIL")
2、问题描述
为了描述这个问题,做了下面这个例子。subprocess.run调用了1个需要10s才能执行完的程序,但是却设定了1s的超时时间。理论上这段代码应该在1s后因超时退出,但事实并不如此。
import subprocess
import time
t = time.perf_counter()
args = 'python -c "import time; time.sleep(10)"'
try:
p = subprocess.run(args, shell=True, check=True,timeout=1,stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
except Exception as e:
print(f"except is {e}")
print(f'coast:{time.perf_counter() - t:.8f}s')
在windows上测试:
PS C:\Users\peng\Desktop> Get-ComputerInfo | select WindowsProductName, WindowsVersion, OsHardwareAbstractionLayer
WindowsProductName WindowsVersion OsHardwareAbstractionLayer
------------------ -------------- --------------------------
Windows 10 Pro 2009 10.0.19041.2251
PS C:\Users\peng\Desktop> python .\test_subprocess.py
except is Command 'python -c "import time; time.sleep(10)"' timed out after 1 seconds
coast:10.03642740s
PS C:\Users\peng\Desktop>
在linux上测试:
21:51:31 wp@PowerEdge:~/bak$ cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.3 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.3 LTS"
VERSION_ID="20.04"
21:56:45 wp@PowerEdge:~/bak$ python test_subprocess.py
except is Command 'python -c "import time; time.sleep(10)"' timed out after 1 seconds
coast:1.00303393s
21:57:02 wp@PowerEdge:~/bak$
可见,subprocess.run的timeout参数在windows下并没有生效。subprocess.run执行指定的命令,等待命令执行完成后返回一个包含执行结果的CompletedProcess类的实例。这个函数的原型为:
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, capture_output=False,
shell=False, cwd=None, timeout=None, check=False, encoding=None, errors=None, text=None, env=None, universal_newlines=None)
- args:表示要执行的命令。必须是一个字符串,字符串参数列表。
- stdin、stdout 和 stderr:子进程的标准输入、输出和错误。其值可以是 subprocess.PIPE、subprocess.DEVNULL、一个已经存在的文件描述符、已经打开的文件对象或者 None。subprocess.PIPE 表示为子进程创建新的管道。subprocess.DEVNULL 表示使用 os.devnull。默认使用的是 None,表示什么都不做。另外,stderr 可以合并到 stdout 里一起输出。
- timeout:设置命令超时时间。如果命令执行时间超时,子进程将被杀死,并弹出 TimeoutExpired 异常。
- check:如果该参数设置为 True,并且进程退出状态码不是 0,则弹 出 CalledProcessError 异常。
- encoding: 如果指定了该参数,则 stdin、stdout 和 stderr 可以接收字符串数据,并以该编码方式编码。否则只接收 bytes 类型的数据。
- shell:如果该参数为 True,将通过操作系统的 shell 执行指定的命令。
3、问题分析
subprocess.run 会等待进程终止并处理TimeoutExpired异常。在POSIX上,异常对象包含读取部分的stdout和stderr字节。上面测试在windows上失效的主要问题是使用了shell模式,启动了管道,管道句柄可能由一个或多个后代进程继承(如通过shell=True),所以当超时发生时,即使关闭了shell程序,而由shell启动的其他程序,本例中是python程序依然在运行中,所以阻止了subprocess.run退出直至使用管道的所有进程退出。如果改为shell=False,则在windows上也出现1s的结果:
python .\test_subprocess.py
except is Command 'python -c "import time; time.sleep(10)"' timed out after 1 seconds
coast:1.00460970s
可以说这是windows实现上的一个缺陷,具体的可见:
https://github.com/python/cpython/issues/87512
[subprocess] run() sometimes ignores timeout in Windows #87512
subprocess.run() handles TimeoutExpired by terminating the process and waiting on it. On POSIX, the exception object contains the partially read stdout and stderr bytes. For example:
cmd = 'echo spam; echo eggs >&2; sleep 2'
try: p = subprocess.run(cmd, shell=True, capture_output=True,
text=True, timeout=1)
except subprocess.TimeoutExpired as e: ex = e
>>> ex.stdout, ex.stderr
(b'spam\n', b'eggs\n')
On Windows, subprocess.run() has to finish reading output with a second communicate() call, after which it manually sets the exception's stdout and stderr attributes.
The poses the problem that the second communicate() call may block indefinitely, even though the child process has terminated.
The primary issue is that the pipe handles may be inherited by one or more descendant processes (e.g. via shell=True), which are all regarded as potential writers that keep the pipe from closing. Reading from an open pipe that's empty will block until data becomes available. This is generally desirable for efficiency, compared to polling in a loop. But in this case, the downside is that run() in Windows will effectively ignore the given timeout.
Another problem is that _communicate() writes the input to stdin on the calling thread with a single write() call. If the input exceeds the pipe capacity (4 KiB by default -- but a pipesize 'suggested' size could be supported), the write will block until the child process reads the excess data. This could block indefinitely, which will effectively ignore a given timeout. The POSIX implementation, in contrast, correctly handles a timeout in this case.
Also, Popen.exit() closes the stdout, stderr, and stdin files without regard to the _communicate() worker threads. This may seem innocuous, but if a worker thread is blocked on synchronous I/O with one of these files, WinAPI CloseHandle() will also block if it's closing the last handle for the file in the current process. (In this case, the kernel I/O manager has a close procedure that waits to acquire the file for the current thread before performing various housekeeping operations, primarily in the filesystem, such as clearing byte-range locks set by the current process.) A blocked close() is easy to demonstrate. For example:
args = 'python -c "import time; time.sleep(99)"'
p = subprocess.Popen(args, shell=True, stdout=subprocess.PIPE)
try: p.communicate(timeout=1)
except: pass
p.kill() # terminates the shell process -- not python.exe
with p: pass # stdout.close() blocks until python.exe exits
The Windows implementation of Popen._communicate() could be redesigned as follows:
- read in chunks, with a size from 1 byte up to the maximum available,
as determined by_winapi.PeekNamedPipe()- write to the child's
stdinon a separate thread- after
communicate()has started, ensure that synchronous I/O in worker
threads has been canceled viaCancelSynchronousIo()before closing
the pipes.
The _winapi module would need to wrap OpenThread() and CancelSynchronousIo(), plus define the TERMINATE_THREAD (0x0001) access right.
With the proposed changes, subprocess.run() would no longer special case TimeoutExpired on Windows.
BrokenPipeError错误和subprocess.run()超时参数在Windows上无效的更多相关文章
- MySQL 各种超时参数的含义
MySQL 各种超时参数的含义 今日在查看锁超时的设置时,看到show variables like '%timeout%';语句输出结果中的十几种超时参数时突然想整理一下,不知道大家有没有想过,这么 ...
- php-fpm nginx 超时参数设置
php-fpm:request_terminate_timeout = 30php.ini:max_execution_time = 30 request_terminate_timeout 适用于, ...
- Flink run提交参数
折腾了好几天,终于搞定了Flink run提交参数,记录一下. 背景: 之前一直报错,akka,AskTimeoutException,尝试添加akka.ask.timeout=120000s, 依然 ...
- Delphi HTTPRIO控件怎么设置超时参数
HTTPRIO控件怎么设置超时参数 //HTTPRIO1: THTTPRIO 设置5分钟超时 HTTPRIO1.HTTPWebNode.ConnectTimeout := 5000; Connect ...
- subprocess.run()用法python3.7
def run(*popenargs, input=None, capture_output=False, timeout=None, check=False, **kwargs): "&q ...
- 如何查看docker run启动参数命令
通过runlike去查看一个容器的docker run启动参数 安装pip yum install -y python-pip 安装runlike pip install runlike 查看dock ...
- Docker run 命令参数及使用
Docker run 命令参数及使用 Docker run :创建一个新的容器并运行一个命令 语法 docker run [OPTIONS] IMAGE [COMMAND] [ARG...] OPTI ...
- Oracle 统计量NO_INVALIDATE参数配置(上)
转载:http://blog.itpub.net/17203031/viewspace-1067312/ Oracle统计量对于CBO执行是至关重要的.RBO是建立在数据结构的基础上的,DDL结构.约 ...
- Http multipart/form-data多参数Post方式上传数据
最近,工作中遇到需要使用java实现http发送get.post请求,简单的之前经常用到,但是这次遇到了上传文件的情况,之前也没深入了解过上传文件的实现,这次才知道通过post接口也可以,是否还有其他 ...
- HTTP 错误 404.2 - Not Found 由于 Web 服务器上的“ISAPI 和 CGI 限制”列表设置,无法提供您请求的页面
详细错误:HTTP 错误 404.2 - Not Found. 由于 Web 服务器上的“ISAPI 和 CGI 限制”列表设置,无法提供您请求的页面. 出现环境:win7 + IIS7.0 解决办法 ...
随机推荐
- Windows打印服务器上无法删除打印机
这几天遇到了一个问题,在Windows 2008的打印服务器上的打印机无法删除.具体表现是可以在设备和打印机里删除打印机,然后刷新一下,它们又出来了.这些打印机早就不存在了,并且这些打印机的图标呈半透 ...
- 使用filebeat过滤掉部分字段
host,agent,ecs三个字段也是不让drop的 processors: - drop_fields: fields: ["log","input",&q ...
- mysqld_exporter参数信息
[root@database03 mysqld_exporter]# ./mysqld_exporter --help usage: mysqld_exporter [<flags>] F ...
- Docker搭建自己的Gitlab CI Runner
转载自:https://cloud.tencent.com/developer/article/1010595 1.Gitlab CI介绍 CI:持续集成,我们通常使用CI来做一些自动化工作,比如程序 ...
- 「Chroot环境」Debian Testing amd64 on arm64
这个是适用于ARM64环境的AMD64 Debian Testing系统.基于FEX转译.这个系统运行在ARM64的手机和电脑上,运行的软件是AMD64(X64)格式.下载链接提供桌面版和基础版.适用 ...
- 聊聊Vim的工作原理
聊聊Vim的工作原理 日常里一直在用Vim这个编辑器,前阵子学习关于Linux中的fd(文件描述符)时,发现vim的进程描述符会比上一个自动加一,后续了解到vim的工作原理后,解开了这个疑问,所以记录 ...
- <一>关于进程虚拟地址空间区域内存划分和布局
C++代码在编译完成后会生产.exe程序(windows平台), .EXE以文件的形式存储在磁盘上,当运行.exe程序的时候 操作系统会将磁盘上的.exe文件加载到内存中,那么在加载到内存中的时候,操 ...
- CRT & EXCRT 学习笔记
这玩意解决的是把同余方程组合并的问题. CRT的核心思想和拉格朗日插值差不多,就是构造一组\(R_i\)使得$\forall i,j(i \neq j) $ \[R_im_i = 1, R_im_j ...
- JavaScript基础&实战(1)js的基本语法、标识符、数据类型
文章目录 1.JavaScript简介 2.输出语句 2.1 代码块 2.2 测试结果 3.JS编写位置 3.1代码 3.2 测试结果 4.基本语法 4.1 代码 5.标识符 5.1 代码 6.数据类 ...
- 齐博X1-栏目的终极方法get_sort
本节说明栏目的最终方法get_sort 我们之前讲的一系列fun函数调用栏目的方法都是基于get_sort这个公共方法而来 我们来看下这个函数的具体参数这个方法有四个参数: id:也就是栏目id,经常 ...