You need to enable JavaScript to run this app. fbprophet屏蔽pystan输出时触发Too many open files报错的解决咨询
优惠活动
大模型
产品
解决方案
定价
更多

fbprophet屏蔽pystan输出时触发Too many open files报错的解决咨询

自定义输出屏蔽逻辑的问题根因

你遇到的OSError: [Errno 24] Too many open files不是__exit__里没关闭null设备文件导致的,核心是原实现存在文件描述符(fd)泄漏:

  • 原类在__init__里通过os.dup(1)、os.dup(2)备份标准输出、标准错误的流副本时,会生成两个独立的fd表项,但整个__exit__流程里,从来没有对这两个备份出来的fd执行关闭操作。
  • 每执行一次with suppress_stdout_stderr():的代码块,就会泄漏2个fd。按照单进程累计15万次模型拟合的规模,累积泄漏的fd数会达到30万,远超Linux默认单进程1024个fd的软上限,哪怕null设备的fd每次都正常关闭,最终也会把进程的fd配额占满,触发报错。
  • 额外说明代码里的笔误:Model类的__init__方法缺少self作为第一个参数,实例化时会直接抛出语法相关的错误,和本次fd泄漏问题无关但需要修正。
最小改动的修复方案

如果想保留原来自定义类的实现,只需要在__exit__的收尾部分,把两个备份用的fd也补上关闭操作即可:

def __exit__(self, *_):
    # 还原真实输出流
    os.dup2(self.save_fds[0], 1)
    os.dup2(self.save_fds[1], 2)
    # 关闭之前打开的所有fd,补上之前漏写的备份fd关闭逻辑
    os.close(self.null_fds[0])
    os.close(self.null_fds[1])
    os.close(self.save_fds[0])
    os.close(self.save_fds[1])
更符合Python规范的优雅替代方案

不建议在每次模型拟合时都反复开关fd做流重定向,一方面容易出现资源泄漏,另一方面高频的fd系统调用会带来不必要的性能开销。针对大规模批量跑模型的场景,有两个更稳妥的实现思路:

方案1:全量fd托管的上下文实现

把所有打开的fd统一存放到列表中,通过finally块做强制资源回收,避免漏关任意一个fd:

import os
from contextlib import contextmanager

@contextmanager
def suppress_c_ext_output():
    opened_fds = []
    try:
        # 打开devnull设备
        devnull_fd = os.open(os.devnull, os.O_RDWR)
        opened_fds.append(devnull_fd)
        # 备份原始stdout/stderr
        saved_stdout = os.dup(1)
        saved_stderr = os.dup(2)
        opened_fds.extend([saved_stdout, saved_stderr])
        # 重定向系统级输出到devnull,拦截pystan这类C扩展的直接fd写入
        os.dup2(devnull_fd, 1)
        os.dup2(devnull_fd, 2)
        yield
    finally:
        # 先还原真实输出流
        os.dup2(saved_stdout, 1)
        os.dup2(saved_stderr, 2)
        # 从后往前关闭所有打开的fd,不管流程中有没有异常都能完成回收
        for fd in reversed(opened_fds):
            os.close(fd)

方案2:全局一次性重定向

既然所有Prophet拟合产生的冗余输出都需要屏蔽,完全不需要在每次拟合时临时操作流,程序启动时做一次全局重定向即可。整个进程生命周期只需要固定占用3个fd,根本不会出现累积泄漏:

import os
from contextlib import contextmanager

# 程序启动时一次性初始化全局流
devnull_fd = os.open(os.devnull, os.O_RDWR)
original_stdout = os.dup(1)
original_stderr = os.dup(2)
# 全局把系统级stdout/stderr切到devnull,所有pystan的输出会直接被吞掉
os.dup2(devnull_fd, 1)
os.dup2(devnull_fd, 2)

# 临时切回真实控制台的上下文,用来打印自己的程序日志
@contextmanager
def console_log():
    try:
        os.dup2(original_stdout, 1)
        os.dup2(original_stderr, 2)
        yield
    finally:
        os.dup2(devnull_fd, 1)
        os.dup2(devnull_fd, 2)

# 之后所有Prophet拟合代码不需要再套任何屏蔽输出的上下文,直接运行即可
# 需要打印自己的进度日志时临时切回控制台:
for ticker in tickers:
    with console_log():
        print(f"开始处理标的 {ticker}")
    # 下面的拟合操作输出会被全局屏蔽
    m = Prophet()
    m.fit(history)
关于调高系统fd上限的说明

直接修改Ubuntu系统的最大可打开文件数,本质上是延后问题爆发的时间,并没有解决fd泄漏的根源。哪怕把上限调到65535,按照原实现2个fd/次的泄漏速度,跑3万次还是会把配额占满,而且泄漏的fd会持续占用进程内存,拖慢批量计算的性能,不建议作为长期解决方案。

内容的提问来源于stack exchange,提问作者Lorenz Weidinger

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.05 16:54:03