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上,异常对象包含读取部分的stdoutstderr字节。上面测试在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 stdin on a separate thread
  • after communicate() has started, ensure that synchronous I/O in worker

    threads has been canceled via CancelSynchronousIo() 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上无效的更多相关文章

  1. MySQL 各种超时参数的含义

    MySQL 各种超时参数的含义 今日在查看锁超时的设置时,看到show variables like '%timeout%';语句输出结果中的十几种超时参数时突然想整理一下,不知道大家有没有想过,这么 ...

  2. php-fpm nginx 超时参数设置

    php-fpm:request_terminate_timeout = 30php.ini:max_execution_time = 30 request_terminate_timeout 适用于, ...

  3. Flink run提交参数

    折腾了好几天,终于搞定了Flink run提交参数,记录一下. 背景: 之前一直报错,akka,AskTimeoutException,尝试添加akka.ask.timeout=120000s, 依然 ...

  4. Delphi HTTPRIO控件怎么设置超时参数

    HTTPRIO控件怎么设置超时参数 //HTTPRIO1: THTTPRIO  设置5分钟超时 HTTPRIO1.HTTPWebNode.ConnectTimeout := 5000; Connect ...

  5. subprocess.run()用法python3.7

    def run(*popenargs, input=None, capture_output=False, timeout=None, check=False, **kwargs): "&q ...

  6. 如何查看docker run启动参数命令

    通过runlike去查看一个容器的docker run启动参数 安装pip yum install -y python-pip 安装runlike pip install runlike 查看dock ...

  7. Docker run 命令参数及使用

    Docker run 命令参数及使用 Docker run :创建一个新的容器并运行一个命令 语法 docker run [OPTIONS] IMAGE [COMMAND] [ARG...] OPTI ...

  8. Oracle 统计量NO_INVALIDATE参数配置(上)

    转载:http://blog.itpub.net/17203031/viewspace-1067312/ Oracle统计量对于CBO执行是至关重要的.RBO是建立在数据结构的基础上的,DDL结构.约 ...

  9. Http multipart/form-data多参数Post方式上传数据

    最近,工作中遇到需要使用java实现http发送get.post请求,简单的之前经常用到,但是这次遇到了上传文件的情况,之前也没深入了解过上传文件的实现,这次才知道通过post接口也可以,是否还有其他 ...

  10. HTTP 错误 404.2 - Not Found 由于 Web 服务器上的“ISAPI 和 CGI 限制”列表设置,无法提供您请求的页面

    详细错误:HTTP 错误 404.2 - Not Found. 由于 Web 服务器上的“ISAPI 和 CGI 限制”列表设置,无法提供您请求的页面. 出现环境:win7 + IIS7.0 解决办法 ...

随机推荐

  1. [Python]-openpyxl模块Excel数据处理-读取公式的结果

    日常需要Python来处理各种数据,处理Excel数据常用的库一般有openpyxl.xlrd(读取).xlwt(写入). 经过对比发现openpyxl模块比较好用. openpyxl模块 这篇笔记比 ...

  2. CI/CD集成

    文章转载自:https://kuboard.cn/guide/cicd/ 下图展示了当前比较典型的持续构建集成的一种做法. 在是否自动将最新版本部署到 Kubernetes 环境这个问题上,可能会有多 ...

  3. 使用Kuboard界面在k8s上部署SpringCloud项目

    先安装Ingress Controller 安装Ingress Controller后,其他服务设置Ingress后就可以通过设置的域名进行访问了,就不用通过代理的方式或者ip:port的方式进行访问 ...

  4. Docker 部署 Confluence(破解版)

    一. 说明 1.1 素材 本文采用素材如下: Docker镜像 Github链接(https://github.com/cptactionhank) 破解工具 Gitee链接(https://gite ...

  5. 「Tubian」Tubian0.4!完全开源!

    Tubian是我自己维护的Linux发行版.Tubian基于Debian,提供了开箱既用的Wine和Waydroid,装好就有对Android应用和Windows程序的兼容. Sourceforge. ...

  6. MySQL用户也可以是个角色

    前言 角色(Role),可以认为是一些权限的集合,一直是存在各个数据库中,比如Oracle.SQL Server.OceanBase等,MySQL 自从 8.0 release 才引入角色这个概念. ...

  7. FastJson序列化对象复杂时出错问题解决

    FastJson序列化对象复杂时出错问题解决 针对复杂的对象,如Map<String, List<Map<String, XxxObject<A, B, C>>&g ...

  8. <三>从编译器角度理解C++代码编译和链接原理

    1代码 点击查看代码 **sum.cpp** int gdata=10; int sum(int a,int b){ return a+b; } **main.cpp** extern int gda ...

  9. CentOS 7.9 安装 kafka_2.13

    一.CentOS 7.9 安装 kafka_2.13 地址 https://kafka.apache.org/downloads.html 二.安装准备 1 安装JDK 在安装kafka之前必须先安装 ...

  10. Linux中CentOS 7版本安装JDK、Tomcat、MySQL、lezsz、maven软件详解

    软件安装 在Linux系统中,安装软件的方式主要有四种,这四种安装方式的特点如下: 安装方式 特点 二进制发布包安装 软件已经针对具体平台编译打包发布,只要解压,修改配置即可 rpm安装 软件已经按照 ...