python - Pygraphviz 绘制 170 个图形后崩溃

标签 python python-3.x windows graphviz pygraphviz

我正在使用 pygraphviz 为不同的数据配置创建大量图表。我发现无论在图形中放入什么信息,程序在绘制第 170 个图形后都会崩溃。程序停止时没有生成错误消息。如果绘制这么多图形,是否需要重新设置?

我在 Windows 10 机器、Pygraphviz 1.5 和 graphviz 2.38 上运行 Python 3.7

    for graph_number in range(200):
        config_graph = pygraphviz.AGraph(strict=False, directed=False, compound=True, ranksep='0.2', nodesep='0.2')

        # Create Directory
        if not os.path.exists('Graph'):
            os.makedirs('Graph')

        # Draw Graph      
        print('draw_' + str(graph_number))
        config_graph.layout(prog = 'dot')
        config_graph.draw('Graph/'+str(graph_number)+'.png') 

最佳答案

我能够通过以下方式不断重现该行为:

  • 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) )_0x104567
  • Graphviz 2.42.2 ((pc032) 与 #2 相同。 )

  • 我怀疑代码中某处存在未定义行为( UB ),即使行为是 相同:
  • 可以用于 169 图形
  • 崩溃 170

  • 做了一些调试(在 agraph.py 和 cgraph.dll (write.c) 中添加了一些 print(f) 语句)。 PyGraphviz 调用 Graphviz 的工具 (.exes) 进行许多操作。为此,它使用 subprocess.Popen 并通过其 3 个可用流(stdin、stdout、stderr)与子进程通信。
    从一开始我就注意到 170 * 3 = 510(非常接近 512 (0x200)),但直到后来才注意到我应该有的关注(主要是因为 Python 进程(运行下面的代码) 没有超过 ~150在任务管理器 (TM) 和进程资源管理器 (PE) 中打开句柄)。
    然而,一些谷歌搜索显示:
  • [SO]: Is there a limit on number of open files in Windows (@stackprogrammer's answer)(从这里开始)
  • [MS.Docs]: _setmaxstdio(其中说明( 重点 是我的)):

    C run-time I/O now supports up to 8,192 files open simultaneously at the low I/O level. This level includes files opened and accessed using the _open, _read, and _write family of I/O functions. By default, up to 512 files can be open simultaneously at the stream I/O level. This level includes files opened and accessed using the fopen, fgetc, and fputc family of functions. The limit of 512 open files at the stream I/O level can be increased to a maximum of 8,192 by use of the _setmaxstdio function.


  • [SO]: Python: Which command increases the number of open files on Windows? (@NorthCat's answer)

  • 以下是我为调试和重现错误而修改的代码。它需要(为了代码简洁,因为同样的事情可以通过 CTypes 实现)PyWin32 包( python -m pip install pywin32 )。
    代码00.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 错误吗?),但从我的角度来看,一旦子进程结束,它的流应该关闭,但我不知道如何强制它(这将是一个正确的修复 )
  • 此外,行为( 崩溃 )尝试 写入 ( 似乎未打开 504567919 位 f04567919 不限制 509 a 507 507 507 507 507 507 507 507 507 507 507 507 507 508
  • 作为一种解决方法,可以增加最大打开 fds 数量。基于以下不等式: 3 * (graph_count + 1) <= max_fds ,您可以了解数字。从那里,如果您将限制设置为 8192(我没有对此进行测试),您应该能够处理 2729 个图形(假设代码没有打开额外的 fds)

  • 旁注 :
  • 在调查过程中,我遇到或注意到几个相邻的问题,我试图解决这些问题:
  • Graphviz:
  • [GitLab]: graphviz/graphviz - [Issue #1481]: MSB4018 The NativeCodeAnalysis task failed unexpectedly. ( 合并于 20200406 )

  • PyGraphviz:
  • [GitHub]: pygraphviz/pygraphviz - AGraph Graphviz handle close mechanism


  • 这种行为还有一个问题(可能是同一作者):[GitHub]: pygraphviz/pygraphviz - Pygraphviz crashes after drawing 170 graphs
  • 关于python - Pygraphviz 绘制 170 个图形后崩溃,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60876623/

    相关文章:

    python - 为不同视频的视频帧创建新目录时出错

    python - Matplotlib xtick 给出 "The truth value of an array with more than one element is ambiguous"

    python-3.x - 按特定月份切片

    c++ - EnterCriticalSection 似乎没有阻塞

    windows - 从嵌套的批处理文件中退出

    c# - 每个按钮单击的增量 C# Desktop

    python - 如何使用 python 在图形中添加标记,如下所示?

    python - SparkMagic PySpark3 与 Livy 在 Cloudera 上进行 session

    javascript - 在 python 服务器上从 Fabric.js JSON 构造图像

    python - “SQLite”数据库被锁定错误