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
相关产品推荐
相关产品推荐

