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

Python异步:子进程间命名管道双向通信问题求助

使用命名管道与pygls子进程实现双向通信的问题解决

问题分析

你遇到的OSError: [Errno 6] Device not configured错误,核心原因有两点:

  1. 非阻塞模式打开FIFO的时序问题:在非阻塞模式下打开FIFO写端时,如果没有任何进程同时打开该FIFO的读端,系统会抛出此错误。你的主程序在启动子进程前就尝试打开写端FIFO,此时子进程尚未启动,自然没有进程打开对应的读端。
  2. FIFO读写方向完全混乱:主程序和子进程对同一个FIFO的读写方向完全一致(都读同一个FIFO、都写同一个FIFO),导致数据根本无法双向流通。

修正方案

1. 明确FIFO的通信方向

重新定义两个FIFO的职责:

  • fifo_main_to_server:主程序写数据,子进程读数据(作为子进程的输入)
  • fifo_server_to_main:子进程写数据,主程序读数据(作为子进程的输出)

2. 调整FIFO打开顺序

先启动子进程,让子进程先打开对应的FIFO端;或者使用阻塞模式打开FIFO,让系统自动等待另一端连接(更稳定)。

3. 修正后的代码示例

主程序代码

import os
import sys
import asyncio
import logging

logger = logging.getLogger(__name__)

# 明确两个FIFO的职责
fifo_main_to_server = "/tmp/fifo_main_to_server"
fifo_server_to_main = "/tmp/fifo_server_to_main"

# 创建FIFO(不存在则创建)
for fifo_name in [fifo_main_to_server, fifo_server_to_main]:
    if not os.path.exists(fifo_name):
        try:
            os.mkfifo(fifo_name)
        except OSError as e:
            logger.error(f"创建FIFO失败 {fifo_name}: {e}")
            raise e

async def main():
    # 先启动子进程,确保子进程有机会打开FIFO
    process = await asyncio.create_subprocess_exec(
        sys.executable, 'lsp/lsp_server.py', fifo_main_to_server, fifo_server_to_main,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE
    )

    # 用阻塞模式打开FIFO,系统会自动等待另一端连接
    try:
        # 主程序读子进程输出:打开fifo_server_to_main为只读
        fd_server_to_main = os.open(fifo_server_to_main, os.O_RDONLY)
        # 主程序写子进程输入:打开fifo_main_to_server为只写
        fd_main_to_server = os.open(fifo_main_to_server, os.O_WRONLY)
    except OSError as e:
        logger.error(f"打开FIFO失败: {e}")
        raise e

    # 转换为文件对象,无缓冲确保实时通信
    main_read = os.fdopen(fd_server_to_main, 'rb', buffering=0)
    main_write = os.fdopen(fd_main_to_server, 'wb', buffering=0)

    # 示例:向子进程发送LSP初始化请求(根据实际业务调整)
    # main_write.write(b'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}\n')
    # main_write.flush()

    # 等待子进程结束(根据业务逻辑调整)
    await process.wait()

    # 清理资源
    main_read.close()
    main_write.close()

    # 删除FIFO(可选,按需保留)
    for fifo_name in [fifo_main_to_server, fifo_server_to_main]:
        if os.path.exists(fifo_name):
            os.unlink(fifo_name)

if __name__ == "__main__":
    asyncio.run(main())

lsp_server.py代码

import os
import sys
from contextlib import contextmanager
from pygls.server import LanguageServer

server = LanguageServer()

@contextmanager
def open_fifo(name, mode):
    # 用阻塞模式打开,避免无对应端的错误
    fd = os.open(name, mode)
    try:
        if mode & os.O_RDONLY:
            # 二进制无缓冲模式,适配LSP的JSON数据流
            file_obj = os.fdopen(fd, 'rb', buffering=0)
        elif mode & os.O_WRONLY:
            file_obj = os.fdopen(fd, 'wb', buffering=0)
        else:
            raise ValueError("不支持的打开模式")
        yield file_obj
    finally:
        file_obj.close()

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("使用方式: python lsp_server.py <fifo_main_to_server> <fifo_server_to_main>")
        sys.exit(1)
    
    fifo_main_to_server = sys.argv[1]  # 子进程的输入流(读主程序发来的数据)
    fifo_server_to_main = sys.argv[2]  # 子进程的输出流(写数据给主程序)

    with open_fifo(fifo_main_to_server, os.O_RDONLY) as server_stdin, \
         open_fifo(fifo_server_to_main, os.O_WRONLY) as server_stdout:
        print("LSP服务器已启动", flush=True)
        # start_io第一个参数是输入流,第二个是输出流,对应LSP的请求读取和响应写入
        server.start_io(server_stdin, server_stdout)

额外注意事项

  • 避免非阻塞模式滥用:除非有明确的异步处理需求,否则阻塞模式更稳定,能自动处理FIFO的两端连接时序问题。
  • 编码一致性:pygls默认使用UTF-8编码处理LSP数据,如果使用文本模式打开FIFO,需指定encoding='utf-8'。
  • 资源清理:务必在程序结束后删除FIFO文件,避免残留文件影响后续运行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 19:29:55