首页
/ Windows Terminal 中 closeOnExit 配置项与终端连接状态机的演进解析

Windows Terminal 中 closeOnExit 配置项与终端连接状态机的演进解析

2026-09-04 22:30:54作者:牧宁李

本文基于 Windows Terminal 仓库中的设计规格书 Improvements to CloseOnExit,讲解 closeOnExit 配置项从布尔值演进为枚举字符串的完整设计过程,以及 ITerminalConnection 接口为此引入的连接状态机(ConnectionState 枚举与 StateChanged 事件)。读完后,你将理解终端如何区分“正常退出”与“异常失败”两种结束状态、每种 closeOnExit 取值下的具体关闭行为,并能结合仓库源码与单元测试验证这些行为的真实实现。

背景:为什么需要改进 closeOnExit

原始规格书(issue #2563,作者 Dustin Howett,2019 年提出)开篇即指出其动机:

本规格描述了对 closeOnExit 配置文件特性和 ITerminalConnection 接口的改进,它将提供更大的灵活性,并允许我们在面对不可靠软件时给出更合理的默认值。

在旧实现中,closeOnExit 只是一个布尔开关:要么“进程退出就关窗口”,要么“永不自动关”。这在遇到不可靠软件时暴露出两个问题:

  1. 用户 shell 配置错误(比如 commandline 指向不存在的程序)时,终端启动即进程立即退出,closeOnExit: true 会让窗口一闪而过,用户根本看不到任何错误提示;
  2. 进程因报错而退出(退出码非 0)时,用户可能希望窗口保留以查看错误输出,但布尔值无法区分“正常退出”和“失败退出”。

规格书还提到,ConEmu 等其他终端模拟器也有类似特性,可作为设计参考。解决思路是:把“连接是否结束”细化为一个有方向的状态机,让应用(而非连接本身)根据状态与配置共同决定是否关闭面板。

ITerminalConnection 接口的状态机设计

规格书的核心改动是给 ITerminalConnection 接口增加了状态枚举和状态迁移事件:

  • 枚举 TerminalConnection::ConnectionState,定义为:
    • NotConnected:所有新连接从该状态开始;
    • Connecting:连接已发起但尚未完成;
    • Connected:连接处于活动状态;
    • Closing:连接正在关闭(通常是主动请求);
    • Closed:连接已关闭,可以是主动请求,也可以是远端进程成功终止;
    • Failed:连接被非预期地终止(失败)。
  • 事件 StateChanged(ITerminalConnection, IInspectable)IInspectable 参数是为类型化事件处理器所必需,但不携带有效载荷;
  • 原有的 TerminalDisconnected 事件被 StateChanged 取代而移除;
  • 规格书明确要求:符合规范的实现必须把状态视为有向无环图(DAG),状态不允许逆向迁移;并且可以提供一个辅助类来管理状态迁移。

当前仓库中的接口定义与规格书完全一致,见 ITerminalConnection.idl

enum ConnectionState
{
    NotConnected = 0,
    Connecting,
    Connected,
    Closing,
    Closed,
    Failed
};

interface ITerminalConnection
{
    void Initialize(Windows.Foundation.Collections.ValueSet settings);
    void Start();
    void WriteInput(Char[] data);
    void Resize(UInt32 rows, UInt32 columns);
    void Close();

    event TerminalOutputHandler TerminalOutput;
    event Windows.Foundation.TypedEventHandler<ITerminalConnection, Object> StateChanged;

    Guid SessionId { get; };
    ConnectionState State { get; };
};

规格书中提到的“辅助类管理状态迁移”由 BaseTerminalConnection.h 落实:基类持有 _connectionState 成员,暴露 State() 只读访问器和 StateChanged 类型化事件,所有连接实现(ConPTY 连接、Azure 连接等)通过基类的状态迁移逻辑更新状态并触发 StateChanged

TerminalControl 的事件投影

规格书规定:是否关闭承载连接的面板由应用层决定,因此 TerminalControl 上表达力不足的 Close 事件被移除,替换为 ConnectionStateChanged 事件;同时 TerminalControl 新增 ConnectionState 属性,直接投影其连接的 State(规格书注明这是为将来 Xaml 数据绑定预留的,见“未来考虑”一节)。

在应用层,这一事件链路的消费方是 TerminalPaneContent.cpp:构造函数中通过 _setupControlEvents() 订阅 TerminalControl.ConnectionStateChanged,由 _controlConnectionStateChangedHandler 处理,其核心逻辑忠实还原了规格书中的职责划分(“Pane 负责根据所承载 profile 的配置做出最终关闭决定”):

safe_void_coroutine TerminalPaneContent::_controlConnectionStateChangedHandler(...)
{
    ConnectionStateChanged.raise(sender, args);
    auto newConnectionState = ConnectionState::Closed;
    if (const auto coreState = sender.try_as<ICoreState>())
    {
        newConnectionState = coreState.ConnectionState();
    }
    const auto previousConnectionState = std::exchange(_connectionState, newConnectionState);
    if (newConnectionState < ConnectionState::Closed)
    {
        // Pane doesn't care if the connection isn't entering a terminal state.
        co_return;
    }
    ...
    const auto mode = _profile.CloseOnExit();
    if (
        (mode == CloseOnExitMode::Always) ||
        (mode != CloseOnExitMode::Never && newConnectionState == ConnectionState::Closed) ||
        (mode == CloseOnExitMode::Automatic && _isDefTermSession))
    {
        CloseRequested.raise(nullptr, nullptr);
    }
}

从源码结构看,这里还体现了两条规格书精神的延伸:

  • 防误闪保护:如果连接在从未真正 Connected 之前就进入 Failed(例如 startingDirectory 配错导致进程根本没起来),即使配置了 closeOnExit: always 也不会关闭面板——这正是规格书中 Reliability 一节“Windows Terminal 不再因用户 shell 不存在而在启动时立即终止”的落地;
  • Automatic 模式:当前实现比规格书原始三值多出一个 automatic 模式,用于 defterm 会话移交(handoff)等场景,保证此类会话即使命令失败也会关闭面板,避免 Windows Terminal 窗口被随机拉起(源码注释中引用了相关讨论)。

closeOnExit 配置项:取值、默认值与布尔值兼容

规格书规定,原有的布尔型 closeOnExit 被替换为支持枚举字符串的键:

取值 行为
always 承载该 profile 的标签页/面板在连接进入任何终态时总是被关闭
graceful 仅当连接进入 Closed 终态(正常退出)时才关闭;Failed 时保留
never 永不自动关闭

规格书还明确了兼容迁移规则(Compatibility 一节):用户可能在 profiles.json 中遗留布尔值,应按 truegracefulfalsenever 映射,以实现平滑过渡。

当前仓库的实现

设置模型中该配置项的声明见 MTSMSettings.h

X(CloseOnExitMode, CloseOnExit, "closeOnExit", CloseOnExitMode::Automatic)

枚举定义见 Profile.idlNever = 0, Graceful, Always, Automatic。注意两点与原始规格书的差异(属于后续演进的实现事实):

  1. 默认值:规格书建议的默认值是 graceful,而当前默认值为 Automatic(见 defaults.json 中的 "closeOnExit": "automatic")。从 TerminalPaneContent.cpp 的判断逻辑看,automatic 在普通 ConPTY 会话下的表现与 graceful 一致(仅在 Closed 时关闭),差异主要体现在 defterm 会话;
  2. 多出的 automatic 取值:用于上面所述的 defterm 移交场景。

JSON 解析与布尔兼容 shim 实现在 TerminalSettingsSerializationHelpers.h

// - Helper for converting a user-specified closeOnExit value to its corresponding enum
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::CloseOnExitMode)
{
    JSON_MAPPINGS(4) = {
        pair_type{ "always", ValueType::Always },
        pair_type{ "graceful", ValueType::Graceful },
        pair_type{ "never", ValueType::Never },
        pair_type{ "automatic", ValueType::Automatic },
    };

    // Override mapping parser to add boolean parsing
    CloseOnExitMode FromJson(const Json::Value& json)
    {
        if (json.isBool())
        {
            return json.asBool() ? ValueType::Graceful : ValueType::Never;
        }
        return EnumMapper::FromJson(json);
    }
    ...
};

可以看到规格书要求的布尔映射 trueGracefulfalseNever 被逐字实现。此外,未识别的取值会按 Automatic 解析(测试用例中 null 即落到该分支)。

单元测试验证

DeserializationTests.cpp 中的 TestCloseOnExitParsing 用例覆盖了字符串取值与 null 的解析结果:

{ "name": "profile0", "closeOnExit": "graceful" }   // -> CloseOnExitMode::Graceful
{ "name": "profile1", "closeOnExit": "always"   }   // -> CloseOnExitMode::Always
{ "name": "profile2", "closeOnExit": "never"    }   // -> CloseOnExitMode::Never
{ "name": "profile3", "closeOnExit": "automatic"}   // -> CloseOnExitMode::Automatic
{ "name": "profile4", "closeOnExit": null          } // -> CloseOnExitMode::Automatic(未知模式解析为 Automatic)

TestCloseOnExitCompatibilityShim 用例则验证布尔兼容迁移:"closeOnExit": true 解析为 Graceful"closeOnExit": false 解析为 Never,与规格书 Compatibility 一节的映射表完全一致。

UI/UX 设计:终态消息与关闭行为矩阵

规格书的 UI/UX 一节要求:现有 ITerminalConnection 实现在进入 ClosedFailed 状态时,应打印有意义、有用的状态信息,并以 ConPTY 连接为例:

场景 输出示例 状态迁移
伪控制台无法打开或进程启动失败 [failed to spawn 'thing': 0x80070002] Failed
进程非预期退出 [process exited with code 300] Failed
进程正常退出 [process exited with code 0] Closed

规格书强调:最后一条消息无论如何都会打印,不受用户配置影响。由此得到各配置下的行为矩阵(可直接用于对照验证):

用户配置 连接进入 Closed 连接进入 Failed
never(或旧值 false 不关闭,消息保留在屏幕上 不关闭,消息保留在屏幕上
graceful(或旧值 true,及默认的 automatic 自动关闭 不关闭,消息保留在屏幕上供用户查看
always 自动关闭 自动关闭(消息来不及被看到)

这个矩阵与 TerminalPaneContent.cppAlways / != Never && Closed / Automatic && defterm 三个判断分支一一对应。

能力影响:可访问性、可靠性与安全性

规格书 Capabilities 一节对改进的影响做了归纳,结合实现可以逐条印证:

  • 可访问性:无论用户以何种方式使用终端(屏幕阅读器、UI Automation 等),都能得知 shell 是启动失败还是以非预期状态码退出——因为终态消息总是会被打印出来;
  • 可靠性:shell 不存在时 Windows Terminal 不再启动即退出,窗口会保留并显示失败信息,用户有机会修正配置;
  • 安全性:无影响。

未来考虑:数据绑定、阈值关闭与状态指示

规格书 Future considerations 一节给出了三条演进方向,其中一条已在代码结构中留有痕迹:

  1. “仅在 shell 运行超过 X 秒后才按 graceful 规则关闭”——由于状态机能够清晰区分 graceful 与 clumsy 退出,该特性在技术上已具备基础(规格书甚至给出了 { "closeOnExit": "10s" } 的设想);
  2. 连接状态枚举对需要联网的连接很有用Connecting / Failed 等状态对 Azure 等远程连接有直接价值;
  3. Xaml 数据绑定:连接状态通过 TerminalControl 暴露(ConnectionState 属性 + ConnectionStateChanged 事件),可以绑定到其他 Xaml 元素上,为承载终端控件的面板、标签页提供离散 UI 状态。规格书的例子包括:连接已断开的标签页可显示红色边框;非激活标签页进入 Connected 状态时可闪烁提示已就绪。

小结

closeOnExit 的演进是 Windows Terminal 一次典型的“配置项驱动接口重构”案例:为了区分进程的“优雅退出”与“失败退出”,规格书先为 ITerminalConnection 建立了单向迁移的 ConnectionState 状态机,再把状态经 TerminalControlConnectionStateChanged 事件投影到应用层,最终由 Pane 依据 profile 的 closeOnExit 配置做出关闭决定。配置层面则通过枚举字符串(always/graceful/never/automatic)加布尔兼容 shim 的方式平滑迁移了旧设置,并有 DeserializationTests.cpp 的单元测试保障解析行为。理解这条“状态机 → 事件投影 → 配置判断”的完整链路,是掌握 Windows Terminal 会话生命周期管理的关键。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
981
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384