我能够在以下环境下不断重现这种行为:
Python 3.7.6 (pc064 (64位), 然后也是通过 pc032)
PyGraphviz 1.5 (我自己编译的 - 可在 [GitHub]: CristiFati/Prebuilt-Binaries - Various software built on various platforms. 下载(当然是在 PyGraphviz 下)。
也可以查看 [SO]: Installing pygraphviz on Windows 10 64-bit, Python 3.6 (@CristiFati's answer))
Graphviz 2.42.2 ((pc032) 同 #2.)
我怀疑在代码中有某处未定义的行为,即使行为是精确相同的:
进行了一些调试(在 agraph.py 和 cgraph.dll (write.c) 中添加了一些 print(f) 语句)。
PyGraphviz 对许多操作都会调用 Graphviz 的工具(.exe)。为此,它使用 subprocess.Popen 并通过其3个可用流 (stdin, stdout, stderr) 与子进程通信。
从一开始我就注意到 170 * 3 = 510
(非常接近 512 (0x200)),但直到后来我没有给予足够的关注(主要是因为Python进程(运行以下代码)在任务管理器 (TM) 和Process Explorer (PE)中最多只有约150个打开句柄)。
然而,经过一番谷歌搜索之后,发现:
以下是我为了调试和重现错误而修改的代码。为了代码简短起见(因为可以通过CTypes实现相同的效果),它需要PyWin32包(python -m pip install pywin32
)。
code00.py:
import os
import sys
import pygraphviz as pgv
import win32file as wfile
def handle_graph(idx, dir_name):
graph_name = "draw_{:03d}".format(idx)
graph_args = {
"name": graph_name,
"strict": False,
"directed": False,
"compound": True,
"ranksep": "0.2",
"nodesep": "0.2",
}
graph = pgv.AGraph(**graph_args)
img_base_name = graph_name + ".png"
print(" {:s}".format(img_base_name))
graph.layout(prog="dot")
img_full_name = os.path.join(dir_name, img_base_name)
graph.draw(img_full_name)
graph.close()
def main(*argv):
print("OLD max open files: {:d}".format(wfile._getmaxstdio()))
print("NEW max open files: {:d}".format(wfile._getmaxstdio()))
dir_name = "Graph"
if not os.path.isdir(dir_name):
os.makedirs(dir_name)
start = 0
count = 170
step_sleep = 0.05
for i in range(start, start + count):
handle_graph(i, dir_name)
handle_graph(count, dir_name)
if __name__ == "__main__":
print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32, sys.platform))
rc = main(*sys.argv[1:])
print("\nDone.\n")
sys.exit(rc)
输出:
e:\Work\Dev\StackOverflow\q060876623> "e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" ./code00.py
Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 064bit on win32
OLD max open files: 512
NEW max open files: 513
draw_000.png
draw_001.png
draw_002.png
...
draw_167.png
draw_168.png
draw_169.png
Done.
结论:
显然,有一些文件句柄(fd)是打开的,但它们不被TM或PE所"看到"(可能在更低层次上)。然而,我不知道这为什么会发生(是MS UCRT的一个bug吗?),但就我所关心的而言,一旦子进程结束,它的流应该被关闭,但我不知道如何强制执行它(这将是一个适当的修复方法)
此外,在尝试对一个fd(超过限制的)进行写入(而非打开)时的行为(崩溃),似乎有点奇怪
作为解决方法,可以增加最大打开fd数。基于以下不等式:3 * (graph_count + 1) <= max_fds
,您可以对数字有一个了解。从那里开始,如果您将限制设置为8192(我没有测试这个),则应该能够处理2729个图形(假设代码没有打开其他fd)
附注: