Python程序在从子进程调用时永远挂起

2024-09-27 00:21:19 发布

您现在位置:Python中文网/ 问答频道 /正文

pip测试套件使用子进程调用来运行集成测试。最近,一个PR被放置,删除了一些旧的兼容性代码。具体来说,它将b()函数替换为显式使用b""文本。然而,这似乎打破了某些特定子进程调用将永远挂起的情况。更糟糕的是,它只在python3.3(可能只有python3.3.5)上永久挂起,而且在Travis之外无法轻松地复制它。在

相关拉取请求:

其他Pull请求也会出现类似的问题,但是它们在不同版本的Python和不同的测试用例中失败。这些请求包括:

另一个用户今天在IRC上向我报告了一个类似的问题,他们说他们可以在ubuntu14.04上用deadsnakes的python3.3在本地复制它(但不是在OSX上),而不仅仅是在Travis上,就像我迄今为止所能做到的那样。他们给我发送了复制步骤:

$ git clone git@github.com:xavfernandez/pip.git
$ cd pip
$ git checkout debug_stuck
$ pip install pytest==2.5.2 scripttest==1.3 virtualenv==1.11.6 mock==1.0.1 pretend==1.0.8 setuptools==4.0
$ # The below should pass just fine
$ py.test -k test_env_vars_override_config_file -v -s
$ # Now edit pip/req/req_set.py and remove method remove_me_to_block or change its content to print('KO') or pass
$ # The below should hang forever
$ py.test -k test_env_vars_override_config_file -v -s

在上面的例子中,remove_me_to_block方法在任何地方都没有被调用,只要它的存在就足以使测试不被阻塞,而它的不存在(或更改它的内容)就足以使测试块永远保持不变。在

大部分调试都是在PR(https://github.com/pypa/pip/pull/1901)中进行的更改。一次推送一个提交,测试通过,直到应用了这个特定的提交-https://github.com/dstufft/pip/commit/d296df620916b4cd2379d9fab988cbc088e28fe0。具体地说,使用b'\r\n'(entry + endline).encode("utf-8")的更改都会触发它,但是这两种情况都不在{}的执行路径中,这是它无法执行的命令。在

在试图追查这个问题时,我注意到,如果我用(lambda: "something")().encode("utf8")替换至少一个对"something".encode("utf8")的调用,它会起作用。在

在尝试调试这个问题时,另一个问题是我尝试过的各种方法(添加print语句,不使用opatexit函数,使用trollious for async子进程)将问题从特定Python版本的特定测试用例转移到Python不同版本上的不同测试用例。在

我知道如果您直接从^{读/写,subprocess模块可能会死锁。然而,这段代码使用的是communicate()方法,它应该可以解决这些问题。在wait()调用中,communicate()会使进程永远挂起,等待pip进程退出。在

其他信息:

  • 这是非常海森堡,我设法使它离开或转移,基于各种不应该对它有任何影响的事情。在
  • 我一直跟踪pip内部的执行,一直跟踪到代码路径的末尾,直到调用sys.exit()。在
  • sys.exit()替换为os._exit()可以修复所有挂起的问题,但是我不想这样做,因为我们将跳过Python解释器所做的清理工作。在
  • 没有其他线程正在运行(用threading.enumerate验证)。在
  • 我有一些更改的组合,即使没有将subprocess.PIPE用于stdout/stderr/stdin,也会挂起;但是,如果不使用这些更改,其他组合将使它挂起(或者它将转换到不同的测试用例/python版本)。在
  • 它似乎与时间无关,任何特定的提交在影响测试用例/Python上要么100%失败,要么0%失败。在
  • 通常情况下,被更改的代码甚至不会被pip子进程中的特定代码路径执行,但是仅仅是更改的存在似乎就破坏了它。在
  • 我尝试过使用PYTHONDONTWRITEBYTECODE=1禁用字节码生成,这在一个组合中有效果,但在其他组合中没有效果。在
  • 子进程调用的命令在每次调用中都挂起(类似的命令是通过测试套件发出的),但是它确实存在对于特定的提交,总是挂在完全相同的位置。在
  • 到目前为止,除了在测试套件中通过subproccess被调用之外,我完全无法复制这个,但是我不知道它是否与此相关。在

我完全不知道是什么原因造成的。在

更新1

使用faulthandler.dump_traceback_later()我得到了这个结果:

^{pr2}$

这向我暗示,问题可能与垃圾回收和urllib3有关?在pip._vendor.cachecontrol.filewrapper中的Filewrapper被用作urllib3响应对象(它的子类是io.IOBase)的包装器,这样我们就可以启动read()方法来将每次读取调用的结果存储在缓冲区中并返回它,然后,一旦文件被完全使用,运行一个包含该缓冲区内容的回调函数,这样我们就可以在缓存中存储该项。这会以某种方式与GC互动吗?在

更新2

如果我向Filewrapper类添加一个def __del__(self): pass方法,那么在我尝试过的情况下,一切都能正常工作。我进行了测试,以确保这不是因为我碰巧定义了一个方法(有时会“修复”它),将其更改为def __del2__(self): pass,它又开始失败了。我不确定为什么这个方法是正确的,而无操作的方法似乎不是最佳的。在

更新3

在执行挂起的pip命令期间,向stderr添加两次import gc; gc.set_debug(gc.DEBUG_UNCOLLECTABLE)打印的内容,它们是:

gc: uncollectable <CallbackFileWrapper 0x7f66385c1cd0>
gc: uncollectable <dict 0x7f663821d5a8>
gc: uncollectable <functools.partial 0x7f663831de10>
gc: uncollectable <_io.BytesIO 0x7f663804dd50>
gc: uncollectable <method 0x7f6638219170>
gc: uncollectable <tuple 0x7f663852bd40>
gc: uncollectable <HTTPResponse 0x7f663831c7d0>
gc: uncollectable <PreparedRequest 0x7f66385c1a90>
gc: uncollectable <dict 0x7f663852cb48>
gc: uncollectable <dict 0x7f6637fdcab8>
gc: uncollectable <HTTPHeaderDict 0x7f663831cb90>
gc: uncollectable <CaseInsensitiveDict 0x7f66385c1ad0>
gc: uncollectable <dict 0x7f6638218ab8>
gc: uncollectable <RequestsCookieJar 0x7f663805d7d0>
gc: uncollectable <dict 0x7f66382140e0>
gc: uncollectable <dict 0x7f6638218680>
gc: uncollectable <list 0x7f6638218e18>
gc: uncollectable <dict 0x7f6637f14878>
gc: uncollectable <dict 0x7f663852c5a8>
gc: uncollectable <dict 0x7f663852cb00>
gc: uncollectable <method 0x7f6638219d88>
gc: uncollectable <DefaultCookiePolicy 0x7f663805d590>
gc: uncollectable <list 0x7f6637f14518>
gc: uncollectable <list 0x7f6637f285a8>
gc: uncollectable <list 0x7f6637f144d0>
gc: uncollectable <list 0x7f6637f14ab8>
gc: uncollectable <list 0x7f6637f28098>
gc: uncollectable <list 0x7f6637f14c20>
gc: uncollectable <list 0x7f6637f145a8>
gc: uncollectable <list 0x7f6637f14440>
gc: uncollectable <list 0x7f663852c560>
gc: uncollectable <list 0x7f6637f26170>
gc: uncollectable <list 0x7f663821e4d0>
gc: uncollectable <list 0x7f6637f2d050>
gc: uncollectable <list 0x7f6637f14fc8>
gc: uncollectable <list 0x7f6637f142d8>
gc: uncollectable <list 0x7f663821d050>
gc: uncollectable <list 0x7f6637f14128>
gc: uncollectable <tuple 0x7f6637fa8d40>
gc: uncollectable <tuple 0x7f66382189e0>
gc: uncollectable <tuple 0x7f66382183f8>
gc: uncollectable <tuple 0x7f663866cc68>
gc: uncollectable <tuple 0x7f6637f1e710>
gc: uncollectable <tuple 0x7f6637fc77a0>
gc: uncollectable <tuple 0x7f6637f289e0>
gc: uncollectable <tuple 0x7f6637f19f80>
gc: uncollectable <tuple 0x7f6638534d40>
gc: uncollectable <tuple 0x7f6637f259e0>
gc: uncollectable <tuple 0x7f6637f1c7a0>
gc: uncollectable <tuple 0x7f6637fc8c20>
gc: uncollectable <tuple 0x7f6638603878>
gc: uncollectable <tuple 0x7f6637f23440>
gc: uncollectable <tuple 0x7f663852c248>
gc: uncollectable <tuple 0x7f6637f2a0e0>
gc: uncollectable <tuple 0x7f66386a6ea8>
gc: uncollectable <tuple 0x7f663852f9e0>
gc: uncollectable <tuple 0x7f6637f28560>

然后呢

gc: uncollectable <CallbackFileWrapper 0x7f66385c1350>
gc: uncollectable <dict 0x7f6638c33320>
gc: uncollectable <HTTPResponse 0x7f66385c1590>
gc: uncollectable <functools.partial 0x7f6637f03ec0>
gc: uncollectable <_io.BytesIO 0x7f663804d600>
gc: uncollectable <dict 0x7f6637f1f680>
gc: uncollectable <method 0x7f663902d3b0>
gc: uncollectable <tuple 0x7f663852be18>
gc: uncollectable <HTTPMessage 0x7f66385c1c10>
gc: uncollectable <HTTPResponse 0x7f66385c1450>
gc: uncollectable <PreparedRequest 0x7f66385cac50>
gc: uncollectable <dict 0x7f6637f2f248>
gc: uncollectable <dict 0x7f6637f28b90>
gc: uncollectable <dict 0x7f6637f1e638>
gc: uncollectable <list 0x7f6637f26cb0>
gc: uncollectable <list 0x7f6637f2f638>
gc: uncollectable <HTTPHeaderDict 0x7f66385c1f90>
gc: uncollectable <CaseInsensitiveDict 0x7f66385b2890>
gc: uncollectable <dict 0x7f6638bd9200>
gc: uncollectable <RequestsCookieJar 0x7f663805da50>
gc: uncollectable <dict 0x7f6637f28a28>
gc: uncollectable <dict 0x7f663853aa28>
gc: uncollectable <list 0x7f663853a6c8>
gc: uncollectable <dict 0x7f6638ede5f0>
gc: uncollectable <dict 0x7f6637f285f0>
gc: uncollectable <dict 0x7f663853a4d0>
gc: uncollectable <method 0x7f663911f710>
gc: uncollectable <DefaultCookiePolicy 0x7f663805d210>
gc: uncollectable <list 0x7f6637f28ab8>
gc: uncollectable <list 0x7f6638215050>
gc: uncollectable <list 0x7f663853a200>
gc: uncollectable <list 0x7f6638215a28>
gc: uncollectable <list 0x7f663853a950>
gc: uncollectable <list 0x7f663853a998>
gc: uncollectable <list 0x7f6637f21638>
gc: uncollectable <list 0x7f6637f0cd40>
gc: uncollectable <list 0x7f663853ac68>
gc: uncollectable <list 0x7f6637f22c68>
gc: uncollectable <list 0x7f663853a170>
gc: uncollectable <list 0x7f6637fa6a28>
gc: uncollectable <list 0x7f66382153b0>
gc: uncollectable <list 0x7f66386a5e60>
gc: uncollectable <list 0x7f663852f2d8>
gc: uncollectable <list 0x7f66386a3320>
    [<pip._vendor.cachecontrol.filewrapper.CallbackFileWrapper object at 0x7f66385c1cd0>, <pip._vendor.cachecontrol.filewrapper.CallbackFileWrapper object at 0x7f66385c1350>]

这是有用的信息吗?我以前从来没有用过那面旗子,所以我不知道那是不是不寻常。在


Tags: pip方法代码httpsgithubcom进程测试用例
1条回答
网友
1楼 · 发布于 2024-09-27 00:21:19

在Python2中,如果一组对象以链(引用循环)链接在一起,并且至少有一个对象具有__del__方法,则垃圾回收器将不会删除这些对象。如果您有一个引用循环,那么添加一个__del__()方法可能只是隐藏bug(解决bug的方法)。在

根据你的更新3,看起来你有这样的问题。在

相关问题 更多 >

    热门问题