You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python Daemon从外部脚本调用时无法运行,报Bad file descriptor错误

问题:Python-daemon结合asyncio时导入调用抛出Bad file descriptor错误

我使用python-daemon包创建守护进程,代码如下:

def main():
    import daemon
    import os

    here = os.path.dirname(os.path.abspath(__file__))
    out = open("debug.log", "w+")

    with daemon.DaemonContext(working_directory=here, stdout=out):
        import asyncio

        def handle_client(reader, writer):
            print("client connected")

        async def run():
            server = await asyncio.start_server(
                handle_client, "0.0.0.0", 5555, start_serving=True
            )
            print("Listening")
            async with server:
                await server.serve_forever()

        loop = asyncio.get_event_loop()
        loop.run_until_complete(run())


if __name__ == "__main__":
    main()

直接执行python <filename>时运行正常,但从其他Python文件通过from packagename.otherfile import main; main()调用时,会抛出如下错误:

File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/base_events.py", line 629, in run_until_complete
    self.run_forever()
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/base_events.py", line 596, in run_forever
    self._run_once()
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/base_events.py", line 1854, in _run_once
    event_list = self._selector.select(timeout)
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/selectors.py", line 562, in select
    kev_list = self._selector.control(None, max_ev, timeout)
OSError: [Errno 9] Bad file descriptor

原因分析

  • 直接运行脚本时,进程的标准文件描述符(stdin/stdout/stderr)处于有效状态;但导入调用时,父进程的这些描述符可能已被关闭或异常,守护进程继承后无法正常使用。
  • python-daemon的DaemonContext默认会关闭所有非标准文件描述符,而asyncio事件循环依赖的selector引用了原进程的描述符,这些描述符在守护进程化后被关闭,触发Bad file descriptor错误。
  • 父进程可能已提前初始化asyncio事件循环,守护进程继承的旧循环在新环境中无法正常工作。

解决方案

修改后的代码示例

def main():
    import daemon
    import os
    import asyncio

    here = os.path.dirname(os.path.abspath(__file__))
    # 打开日志文件,分别记录标准输出和错误
    out_log = open(os.path.join(here, "debug.log"), "a+")
    err_log = open(os.path.join(here, "error.log"), "a+")

    # 配置守护进程上下文,显式处理文件描述符
    daemon_context = daemon.DaemonContext(
        working_directory=here,
        stdout=out_log,
        stderr=err_log,
        stdin=open(os.devnull, 'r'),  # 将标准输入重定向到null
        # 保留日志文件的描述符,避免被自动关闭
        files_preserve=[out_log.fileno(), err_log.fileno()]
    )

    with daemon_context:
        async def handle_client(reader, writer):
            print("client connected", flush=True)
            writer.close()
            await writer.wait_closed()

        async def run():
            # 在守护进程内创建全新的事件循环,避免继承父进程的循环
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            
            server = await asyncio.start_server(
                handle_client, "0.0.0.0", 5555, start_serving=True
            )
            print("Listening on 0.0.0.0:5555", flush=True)
            async with server:
                await server.serve_forever()

        asyncio.run(run())


if __name__ == "__main__":
    main()

关键修改说明

  • 使用asyncio.run()替代手动获取旧循环,确保在守护进程内初始化全新的事件循环,避免继承父进程的无效资源。
  • 通过DaemonContext的files_preserve参数显式保留日志文件的描述符,防止被默认逻辑关闭。
  • 显式重定向stdin/stdout/stderr,确保守护进程拥有独立的有效标准流。
  • 打印时添加flush=True,确保日志内容立即写入文件,避免缓冲区积压。

额外注意事项

  • 导入调用main()的父进程不要提前初始化asyncio事件循环,否则可能引发资源冲突。
  • 确保守护进程对日志文件所在目录有写入权限。

内容的提问来源于stack exchange,提问作者David White

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 10:01:16