Skip to content

emit build-database 供 IDE 调用:离线缺依赖的错误码、规划期间不泄漏调用方的管道、xlings 调用要有期限(附联网效应、auto_refresh、索引镜像) #648

Description

@Sunrisepeak

概述

mcppls(mcpp-language-server)在后台调用 mcpp emit build-database --format json#636,2026.9.15.1 起提供)读取工程的构建描述。2026-09-16 在 openxlings/xlings 上:

  • 一次调用在规划阶段同步执行 xlings update,这条网络连接挂了约 11.5 分钟,mcpp 一直阻塞。
  • 调用方在 5 分钟时结束了 mcpp,但 mcpp 启动的 xlings update 仍持有调用方的标准输出管道,调用方又多等了 6.5 分钟才读到管道结束。
  • 同一工程离线运行只要 2.6 s,得到的构建描述与联网成功时逐字节相同。

mcppls 这边会改为默认离线调用,并自己兜住挂起(见文末)。下面六项需要 mcpp 适配:前三项是这次挂起的原因,同样影响在终端里运行 mcpp build 的用户;后三项让调用方能正确判断和提示。

现场(xlings,mcpp 2026.9.15.1,Linux)

  1. VS Code 经 bin/code 启动(VSCODE_CLI=1,不解析登录 shell 的环境),扩展宿主里没有用户终端中的代理变量与 PATH。mcppls 在此环境中运行 mcpp emit build-database --format json
  2. mcpp 规划时执行:
    sh -c "cd ~/.mcpp/registry && env -u XLINGS_PROJECT_DIR -u XLINGS_ACTIVE_SUBOS PATH=… XLINGS_HOME=~/.mcpp/registry xlings update 2>&1 </dev/null"
    
    并阻塞在读取上。
  3. xlings update 连 GitHub(IPv6,443 端口),发送队列停在 85 字节,一直没有确认,也没有超时。
  4. 5 分钟时调用方结束 mcpp(只结束直接子进程)。shxlings update 被 init 收养,它们的 fd 3 与调用方读取 mcpp 标准输出的描述符是同一个管道(/proc/<pid>/fd 核对)。调用方直到 xlings update 退出才读到管道结束。
  5. 对照:MCPP_OFFLINE=1 mcpp emit build-database --format json 在用户终端环境与上述编辑器环境中都是 2.6 s、退出 0,输出逐字节相同(2,608,930 字节)。

原因(代码 @ 2fc7b5b

事实 位置
emit build-databasemcpp build 走同一个 prepare_build,依赖与工具链解析真实执行,可能刷新索引 cmd_build.cppm L342-374
规划期间 StdoutToStderrdup(1) 保存原标准输出,没有 close-on-exec,最低空闲描述符正是 3;规划中经 popen 启动的程序都继承调用方的管道 terminal.cppm L76-85
索引刷新 update_indexrun_streamingpopen 加阻塞 fgetspclose,没有期限;只在非零退出时重试 3 次,挂起时无效 process.cppm L550-579xlings.cppm L1948-1970
离线时缺包,失败只有通用错误码 MCPP_BUILD_DATABASE_PLAN_FAILED,调用方只能匹配消息文本 cmd_build.cppm L375package_fetcher.cppm L1114-1119
每次运行的信封 effects 只有 read-projectwrite-global-cache(和 exec-build-script),这次刷新了索引也不含 network cmd_build.cppm L427-428
auto_refresh 只约束 decide_for_dependencydecide_for_miss;工具链自动安装前的 ensure_official_package_index_fresh、工程自定义索引的首次同步不受它约束 index_refresh.cppm L284xlings.cppm L1988-2018prepare.cppm L4644-4662
mcpp-index 的仓库与制品地址只有 GitHub 一套;CN 形态记为“待镜像仓真实部署后再扩” config.cppm L55-60CHANGELOG L6319

ensure_official_package_index_fresh 的注释已经提到 “xlings update … stalls for minutes on slow/blocked networks (the Termux first-run / build hang)”。按需触发的刷新减少了次数,但一旦触发,仍然没有期限。

需要的适配

A1 离线缺依赖:专门的错误码

  • 现状: 离线时缺包、缺工具链或索引未初始化,信封里只有 MCPP_BUILD_DATABASE_PLAN_FAILED,缺的是什么只写在消息文本里。
  • 为什么需要: IDE 默认离线调用。“需要下载”与“构建描述写错了”要给用户不同的提示和动作:前者提示在终端运行一次 mcpp build,后者提示查看诊断。调用方不应依赖消息文本。
  • 建议:
    • 离线时因缺少可下载内容而失败,用专门的错误码,例如 MCPP_OFFLINE_DOWNLOAD_REQUIRED
    • 每个缺少的包、工具链或索引各给一条诊断,消息中含名称、版本、所在索引,以及用户应运行的命令。
    • 其他规划失败仍用原错误码。
  • 验收: e2e 用例:删除一个依赖包的本地安装,离线运行 emit build-database,信封中出现该错误码,诊断中含包名与版本,退出码 1。

A2 规划期间不把调用方的描述符交给子进程

  • 现状: StdoutToStderrdup(1) 保存的调用方管道没有 close-on-exec,经 popen/std::system 启动的 xlings 与它的后代都继承了这个管道。mcpp 自己结束后,调用方仍读不到管道结束。
  • 为什么需要: 任何以管道读取 mcpp 输出的调用方(IDE、CI 脚本、$(mcpp …))都可能被 mcpp 的后代无限期挂住。这次多挂了 6.5 分钟。
  • 建议:
    • POSIX:fcntl(1, F_DUPFD_CLOEXEC, 3) 保存。
    • Windows:保存为不可继承的句柄。
    • 启动外部程序时,只让子进程继承 0–2(close_range/posix_spawn_file_actions_addclosefrom_np,或逐个设 FD_CLOEXEC)。
  • 验收: e2e 用例:以管道读取 emit build-database 的输出,并用一个会挂起的 xlings 替身([xlings] binary 指向它)。结束 mcpp 后,调用方 1 s 内读到管道结束;替身进程的 /proc/<pid>/fd 中没有调用方的管道。

A3 xlings 调用有期限,并与 mcpp 一起结束

  • 现状: 索引刷新与安装调用 xlings 时没有任何期限。mcpp 被结束时,xlings 进程继续运行。
  • 为什么需要: 网络不通、代理未生效、IPv6 黑洞时,emit build-databasemcpp build 会一直挂住,只能手动结束。
    • prepare.cppm 已规定“刷新失败只警告,之后仍用本地数据解析”(L4465-4472),但挂起的刷新永远不会“失败”。
  • 建议:
    • 索引刷新设整体期限(例如 60 s,可在 config.toml 中配置)。超时视为刷新失败,按既有语义继续用本地数据;本地数据足够时,命令照常成功。
    • 下载与安装的期限可以更长,但不应无限。
    • xlings 在独立进程组中运行;mcpp 收到 SIGTERM、SIGINT,或自身期限到时,结束该进程组。
  • 验收:
    • e2e 用例:xlings 替身挂起时,若本地数据足以解析,emit build-database 在期限后返回并成功。
    • 结束 mcpp 后,没有残留的替身进程。

A4 信封的 effects 报告实际发生的联网

  • 现状: --protocol-version 的静态表中,emit build-databasenetwork;每次运行的信封从不含 network
  • 为什么需要: 调用方据此判断这次结果是否依赖网络,并在日志与诊断报告中说明“这次读取构建描述时访问了网络”。
  • 建议: 本次运行实际执行了索引刷新、下载或 git 访问时,把 network 加入信封的 effects
  • 验收: 刷新后运行的信封含 network--offline 运行的信封不含。

A5 auto_refresh = false 约束所有隐式刷新

  • 现状: 注释写着设为 false 就“require an explicit mcpp index update”,但工具链自动安装前的刷新与自定义索引首次同步仍会联网。
  • 为什么需要: 用户与 IDE 需要一个仅靠配置就可靠的开关。
  • 建议: 二选一:
    • 让这两处也受 auto_refresh 约束;
    • config.toml 注释与文档中写明只有 --offline 能完全禁止联网。
  • 验收: auto_refresh = false 且缺少工具链时,不执行 xlings update,诊断提示运行 mcpp index update

A6 mcpp-index 的镜像地址

  • 现状: mcpp-index 的仓库与制品地址只有 GitHub 一套。mirror = CN 选择的是包描述中的下载地址;这次现场 mcpp 私有仓库的 .xlings.json"mirror": "CN"xlings update 连接的仍是 GitHub。
  • 为什么需要: 在访问 GitHub 不稳定的网络中,镜像只覆盖了下载,刷新仍可能挂起(这次正是 GitHub 连接)。
  • 建议: 部署 xlings-res/mcpp-index 的 gitcode 镜像后,为默认索引加上 {"GLOBAL": …, "CN": …} 形态,刷新时按 mirror 选择。
  • 验收: mirror = CN 时,mcpp index update 不访问 github.com。

mcppls 这边的做法(不依赖上述适配也会做)

  • 默认以 MCPP_OFFLINE=1 调用 emit build-database。离线缺依赖时,状态栏提示用户在自己的终端运行 mcpp build:代理与凭据只有用户的终端才有。A1 之后改用错误码识别。
  • 外部程序在独立进程组中运行。程序退出后,最多再读 1 s 输出;超时结束整组。
  • 缓存上一次成功的构建描述,启动时先用它;mcpp 慢、失败或挂起都不影响语言功能。
  • 设计文档:.agents/docs/2026-09-16-mcppls-build-description-design.md(第 1.3 节为本 issue 的代码核对,第 7 节为草稿)

相关

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions