我能够通过以下方式不断重现该行为:
-
Python 3.7.6 (pc064 (64bit),然后还有 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.)
我怀疑代码中某处存在未定义行为 (UB),即使该行为准确地一样的:
做了一些调试(在 agraph.py 和 cgraph.dll 中添加了一些 print(f) 语句(write. c))。
PyGraphviz 调用 Graphviz 的工具 (.exes) 进行许多操作。为此,它使用 subprocess.Popen 并通过其 3 个可用流(stdin、stdout、stderr)。
从一开始我就注意到170 * 3 = 510(非常接近512 (0x200)),但直到后来我才注意到(主要是因为 Python 进程(运行下面的代码)在任务管理器中打开的句柄不超过 ~150 个(TM) 以及 Process Explorer (PE))。
然而,一点 Google 透露:
以下是我为调试和重现错误而修改的代码。它需要(为了代码简洁,同样的事情可以通过 CTypes 实现)PyWin32 包 (python -m pip install pywin32)。
code00.py:
#!/usr/bin/env python
import sys
import os
#import time
import pygraphviz as pgv
import win32file as wfile
def handle_graph(idx, dir_name):
graph_name = "draw_{0: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)
# Draw Graph
img_base_name = graph_name + ".png"
print(" {0: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() # !!! Has NO (visible) effect, but I think it should be called anyway !!!
def main(*argv):
print("OLD max open files: {0:d}".format(wfile._getmaxstdio()))
# 513 is enough for your original code (170 graphs), but you can set it up to 8192
wfile._setmaxstdio(513) # !!! COMMENT this line to reproduce the crash !!!
print("NEW max open files: {0:d}".format(wfile._getmaxstdio()))
dir_name = "Graph"
# Create Directory
if not os.path.isdir(dir_name):
os.makedirs(dir_name)
#ts_global_start = time.time()
start = 0
count = 169
#count = 1
step_sleep = 0.05
for i in range(start, start + count):
#ts_local_start = time.time()
handle_graph(i, dir_name)
#print(" Time: {0:.3f}".format(time.time() - ts_local_start))
#time.sleep(step_sleep)
handle_graph(count, dir_name)
#print("Global time: {0:.3f}".format(time.time() - ts_global_start - step_sleep * count))
if __name__ == "__main__":
print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
main(*sys.argv[1:])
print("\nDone.")
输出:
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)] 64bit 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.
结论:
- 显然,一些文件句柄 (fds) 是打开的,尽管 TM 或 PE 没有“看到”它们(可能它们处于较低水平)。但是我不知道为什么会发生这种情况(它是 MS UCRT 错误吗?),但就我而言,一旦子进程结束,它的流应该被关闭,但我不知道如何强制它(这将是一个适当的修复)
- 此外,尝试写入(未打开)到fd(超过限制),似乎有点奇怪
- 作为一种解决方法,可以增加 max 打开 fd 的数量。根据以下不等式:
3 * (graph_count + 1) <= max_fds,您可以对数字有所了解。从那里开始,如果您将限制设置为 8192(我没有对此进行测试),您应该能够处理 2729 图表(假设有代码没有打开额外的fd)
旁注: