一个 PDF 卡死整个桌面:有人终于修掉了 Enlightenment E16 里潜伏 20 年的老 bug

开发工具 2026年4月15日
一个 PDF 卡死整个桌面:有人终于修掉了 Enlightenment E16 里潜伏 20 年的老 bug
一位仍在日常使用 1997 年窗口管理器 Enlightenment E16 的开发者,最近修掉了一个可追溯到 2006 年的顽固 bug:只要打开某个标题过长的 PDF,整个桌面就会卡死。更耐人寻味的是,问题不在复杂图形栈,而是在一个“看起来很聪明”的字符串截断算法——它借用了牛顿迭代的思路,却忘了给自己留条后路。

一场由窗口标题引发的“桌面冻住”事故

科技圈喜欢讲新故事:新模型、新芯片、新手机、新一代桌面环境。可这一次,最有意思的新闻,偏偏来自一套诞生于 1997 年的 Linux 窗口管理器——Enlightenment E16。

事情的起点颇有老派极客喜剧的味道。作者正在赶课件,打开一份用 LaTeX 排版的 PDF,结果不是软件闪退,也不是渲染花屏,而是整个桌面直接“定住”。鼠标不动,界面不响,X11 会话只能从 TTY 强杀。更神奇的是,这不是偶发现象,而是稳定复现:只要打开那一个 PDF,桌面必挂。

如果你没接触过 E16,这里补一句背景。Enlightenment 早年以华丽主题和轻量设计闻名,E16 是那个年代留下来的经典分支。今天主流 Linux 桌面早已转向 GNOME、KDE,窗口合成、Wayland 支持、HiDPI 适配一应俱全;而 E16 还吸引着一批坚定用户,因为它足够轻、足够快、足够可折腾。作者提到,E16 的峰值内存占用只有 24MB。放在今天这个连聊天软件都恨不得吃掉几百兆内存的时代,这个数字有点像古董车一脚点火就能上路,让人既怀旧又佩服。

bug 不在 PDF,也不在字体库,而在“太聪明”的算法

调试过程很像侦探小说。作者用 gdb 挂到运行中的 E16 进程上,发现调用栈总会卡在 imlib2 的字体缓存附近。表面看像是字体系统出问题了,但反复附加调试后,现象又不太对:程序不是死锁,而是在不断工作;每次卡住时,字形索引都在变化,说明底层字体测量仍在推进。

真正的“元凶”藏在更外层:E16 为了把过长的窗口标题塞进有限的装饰栏,会尝试在中间打省略号,也就是把“Kickoff.pdf — Introduction to Information Theory Session 1...”这种长标题裁成“Kickoff.pdf ... Session 1”一类的形式。这本来是个很常见的 UI 细节,用户几乎不会注意到,直到某一天它把整个桌面拖下水。

问题在于,E16 这里采用的不是简单粗暴地一次删一个字符,而是一个颇有数学味道的“牛顿法式”搜索:根据当前文本宽度与目标宽度的差距,估算应该再删掉多少字符,以便更快逼近目标。这个思路本身不笨,甚至可以说有点优雅——老程序员很喜欢这种用一点数学直觉换性能的写法。

但算法世界里,聪明常常和危险只有一线之隔。牛顿迭代法最经典的教训之一就是:它不保证收敛。起点不对、导数估计不准、容差设置太苛刻,都可能让它在两个答案之间来回横跳,永远走不出来。而 E16 的这段代码,恰好把这些坑几乎踩了个遍:它用“平均每字符像素宽度”来近似导数,退出条件又设得很紧,最后就在两个截断方案之间无限振荡。于是,一个窗口标题足够长、字体足够窄、标题栏空间又刚好不够的 PDF,就能让整个循环跑到天荒地老。

说白了,这不是“高深 bug”,反而是那类最典型、最人性化的 bug:工程师当年写代码时大概觉得自己想得很周到,结果 20 年后现实世界拿出一个边界条件,轻轻一碰,系统就趴下了。

修复并不华丽,但很像成熟工程的样子

作者给出的修复方案,几乎没有什么炫技成分,却非常像一个被 bug 教育过很多次的人会做的事。

第一刀,是给迭代加上上限。超过 32 次还没收敛,就别继续“相信数学”了:如果当前结果已经能放进标题栏,那就接受;如果还放不下,就老老实实多删一个字符再试。这一步很关键,因为它把“理论上可能无限循环”的问题,硬生生改造成“最坏情况下也会在有限时间内结束”的问题。对于桌面系统这种基础软件来说,能保证退出,往往比追求最优雅的解更重要。

第二刀,是给 nuke_count 设下限,避免负向修正过度,出现头尾重叠的畸形字符串。第三刀,则是确保平均字符宽度 cw 至少为 1,防止在极端测量情况下引出除零风险。这些改动都不大,却很有代表性:真正可靠的软件,往往不是靠某个天才公式撑起来的,而是靠一层层防御式编程把现实中的离谱情况堵住。

我很喜欢这个修复透露出的工程气质。它没有试图证明“我的牛顿法依然伟大”,而是坦率承认:算法会失手,边界条件会背叛你,用户输入不会配合你,旧代码更不会自动变好。于是最务实的答案,是给系统留缓冲区,给失败留出口。

为什么这件小事,在今天反而更值得关注

如果这只是某个冷门窗口管理器修了个老 bug,那它原本不至于成为一条值得拿出来讲的科技新闻。真正让我觉得有意思的,是它恰好击中了当下软件行业的一个敏感点:我们越来越迷信“新”,却越来越少讨论“老系统如何体面地活下去”。

今天的软件世界,一边在高歌猛进,AI 辅助编程把提交代码的门槛降得越来越低;另一边,代码库正以前所未有的速度膨胀。新功能加得快,依赖堆得高,发布节奏越来越紧。表面上,软件在持续更新;实际上,很多产品只是把复杂度不断往后推,把风险打包塞进未来。

这也是为什么这次 E16 的 bug 修复让我有点感慨。一个 20 年前留下的缺口,被一位真实使用它的人,在真实的使用场景里发现、定位、修补。没有 PR 宣传,没有“大版本焕新”,没有“全面重构”。只是一个用户兼维护者,因为自己的桌面真被卡死了,于是认认真真把问题搞明白。这种修修补补的过程,某种程度上比新功能发布更接近软件文明最本质的样子。

作者在原文里还顺手吐槽了近期 Linux 稳定内核分支曾引入明显问题,以及 XZ 后门事件带来的供应链阴影。这个延伸并不突兀。它提醒我们:老代码未必天然危险,新代码也绝不天然先进。一个持续被少数懂行的人照料的老项目,有时比一个高速迭代、依赖庞杂、贡献者结构复杂的新项目更可控。至少你知道自己的风险在哪里。

当然,这并不意味着大家都该回去用 1997 年的窗口管理器,更不意味着“旧就是好”。E16 这样的项目同样有技术债,也有现代兼容性局限。可它提出了一个很值得想的问题:当软件行业把全部注意力都放在“发明下一代”时,谁还愿意安静地维护上一代、上上一代,甚至那些看起来不再性感、却仍有人每天在用的系统?

一只长标题 PDF,照出软件世界的真实面目

那份触发 bug 的标题很长,长到有点戏剧性:

Kickoff.pdf — Introduction to Information Theory Session 1: kickoff & first topic

81 个宽字符、约 291 像素的标题栏空间、平均每字符 3 像素左右宽度——就这样,一个普通得不能再普通的窗口标题,成了 20 年技术债的试纸。它没利用漏洞、没做恶意输入、没调用什么黑魔法,只是老老实实地把自己摆在那里,然后让一个边界条件露了馅。

这也是基础软件最迷人的地方:很多决定系统稳定性的,不是那些用户看得见的大功能,而是标题栏、省略号、字符串测宽、缓存命中这些不起眼的角落。平时它们安安静静,一旦出错,影响却可能直接放大到“整个桌面卡死”。

我一直觉得,真正成熟的技术报道,不该只盯着那些轰轰烈烈的新品发布。像这样的故事,同样值得被记录。它让我们看到软件不是抽象的“技术栈”,而是一堆具体判断的总和:某个循环要不要设上限,某个估算公式要不要容错,某个看似很稳的逻辑,要不要替最坏情况留一个出口。

从这个角度看,这次修 bug 的新闻甚至有点温柔。它不是在证明“旧软件不朽”,而是在告诉我们:只要还有人愿意理解代码、尊重边界条件、对自己的工具负责,再老的系统也有机会继续优雅地活着。相比那些热闹一时的产品发布,这种朴素的工程修养,也许才更接近技术真正的长期价值。

Summary: 这次 E16 修复 20 年老 bug,表面看只是一个冷门窗口管理器的小插曲,实际上却点中了现代软件最核心的矛盾:我们擅长制造新复杂度,却不总擅长驯服旧复杂度。我的判断是,未来几年,类似“老项目被重新认真维护”的故事会越来越有现实意义,尤其在供应链安全和软件膨胀问题持续加剧的背景下。能把一个无限循环改成可控失败,远比多做一个花哨功能更值得尊敬。
Enlightenment E16bug 修复窗口管理器Linux 桌面PDF字符串截断算法牛顿迭代X11LaTeX桌面卡死