示意图

> IBus 引擎崩溃 = 桌面崩溃。8 个坑差点让我放弃,到 cddcb40 怎么救场。

本篇是 《TUX IM 开发日志:从零到 0.1》 系列第 2/8 篇。

第 2 篇 · IBus 引擎的第一声惨叫——我是怎么把 daemon 写崩的

第 1 篇里我给自己立了个 flag:任何异常都不能逃出引擎进程。但说实话,写出那句话的时候,我已经崩了三次了。这篇是回顾——讲讲从 aa4b094cddcb40 之间,我到底被哪些 bug 折磨过,以及 cddcb40 是怎么把这些洞一次性堵上的。

第一声惨叫:每个按键变两个字母

刚开始测试拼音输入时,我输入 n,期待 buffer 是 n,结果显示 nn。输入 ni 显示 nii。每个按键都被吃了两遍。

查了半小时才发现原因:IBus 给每个按键发两次事件——一次 press (state=0),一次 release (state=0x40000000 = IBus.ModifierType.RELEASE_MASK)。我的引擎只过滤了 release 事件走 shortcut 路径,但 _handle_key 还是会把它喂给 PinyinMode.feed_key,于是 release 事件再次被当成字母加进 buffer。

修复 commit 25b94ff Filter key-release events in do_process_key_event:把 release 过滤挪到 do_process_key_event 的最顶端,任何路径之前

> 教训:IBus 状态字段不只是修饰位——RELEASE_MASK 是一个独立的事件类型,必须在引擎入口处就拦掉,不能依赖下游模块自己防御。

第二声惨叫:Ctrl+Shift+M 按了没反应

我在配置里写了 cycle_mode = "m",按下去——日志输出 "pressed but no handler"。再按——同样的日志。

两个 bug 叠在一起:

  • shortcut parser 解析错误8c4b8f1):parse_shortcut("m")replace('>', '><').strip('').split('') 处理,结果得到 ['Ctrl', ' 这是被test_engine_flow.py抓到才修的——28 个端到端测试里有一个test_pinyin_with_tone_digits`,就是专门防这种 bug。如果没有测试套件,这个 bug 大概率会跟着版本发布出去。

踩坑时间线

示意图

> 从 aa4b094cddcb40,跨度约 2 周,踩了 8 个坑,几乎每个坑都让 ibus-daemon 自杀过

第八个坑:为什么 ibus-daemon 必自杀

我犯的很多 bug,其实不是单个 bug 多致命——而是任何 bug 都致命。因为 IBus 的进程模型:

  • ibus-daemon 是常驻进程,挂了就要重启整个桌面输入法。
  • 引擎进程异常退出 → ibus-daemon 默认自杀(避免脏状态)。
  • 所以引擎里任何未捕获的 AttributeErrorKeyErrorIndexError,都会让整个桌面失声。

最搞笑的一次:我改了 tux_im/asr/overlay.py 里的 _PI_2 用法,结果输入 j(拼音 j)时,整个 daemon 死了——因为 wubi 启发式判断 _looks_like_wubi 太松,j 被识别成 wubi 编码(实际只有 1 个字符),触发了 overlay 绘制,overlay 里又调了未定义的 _PI_2,异常逃出去。整个上午的工作全没了。

cddcb40:把所有洞一次性堵上

2026 年 6 月 20 日,我做了两件事:

  • 每个 IBus 生命周期 hook 加 try/except——do_enabledo_disabledo_focus_indo_focus_outdo_property_activatedo_process_key_eventdo_reset,全部包了 try/except,并把实现逻辑抽到 _do_*_impl() 私有方法里。
  • 写了 28 个端到端测试tests/test_engine_flow.py)——type → candidates → select → reset 全链路模拟,每个 input mode 至少一个击键序列测试。
    从那以后,引擎再也没有把 daemon 拖死过。后来加了 emoji、Google Pinyin、ASR,每次新增功能都能从测试套件里看到旧的 input mode 没坏——这是 cddcb40 留下的最大遗产。

本篇小结

  • IBus 引擎崩溃 = 桌面崩溃,这是不可违反的硬约束。
  • 8 个坑:release 事件、shortcut 解析、Shift keyval、prop 前缀、tone digit trie key、preedit focus mode、append vs append_text、_PI_2 未定义。
  • cddcb40 的两条腿:crash isolation(catch 一切)+ test suite(28 个端到端测试)。
  • 经验:每加一个新功能,先看测试套件里旧的功能有没有坏;没坏才能 commit。

> 下一篇6 个 bug 修复拼出的核心架构。从 4bee4ca89f4984,讲讲 bug 修复如何反过来塑造项目的架构——不是设计出来的,是长出来的。

最后修改: 2026年7月1日

作者

评论

发表评论

您的邮箱地址不会被公开。