
--- 摄于 2017 年 9 月 藏川线前段
(为什么技术方面文章频次降低了,因为如果 LLM 能简单解决的问题已经不值得写了)
俗话说,重构一时爽,Debug 火葬场。事情的起因,仅仅是我在查阅代码时做了一次“顺手”的性能优化。
最近我翻阅 fiber 的网络层代码,发现其底层基于 Actor 模型,网络消息直接作为事件传入 Actor。这意味着,它不需要依赖 tentacle 库默认提供的聚合功能。
然而,代码里的 tentacle 仍沿用着默认的聚合模式。为了降低无谓的性能损耗,我将其切换成了更轻量的 Spawn 模式,并提交了代码。
结果出乎意料——CI 全线报红,测试用例全军覆没。 哪怕 service control 确实修改了初始化时间,也不至于落得个“全军覆没”的下场。考虑到构建环境极度繁琐,我决定唤醒 Codex 和 Opus,开启这段漫长的“环境构建 + Debug 诊断”之旅。
在 Codex5.3 的辅助下,排查过程层层推进:
StreamHandle 与 tokio 的 split 配合上。至于更深层的碰撞原因,Codex5.3 就束手无策了。无论怎么尝试,它都无法给出能通过测试的修复。僵持四个小时后,我只好让它在 tentacle 库里专门写了一个简化的测试用例,将这个“必挂”的现场稳定固化下来。
带着固化好的失败用例,我直接在 tentacle 项目里向 Opus 4.6 抛出线索:怀疑是 StreamHandle 与 tokio split 的配合缺陷,让它深挖根因。
Opus 吭哧了四五个小时,终于揪出了幕后黑手:在读写分离后,StreamHandle 的 waker 出现了混乱,导致读端 waker 被彻底污染,陷入了“永远无法被唤醒”的假死状态。
问题根源深藏在 Yamux 协议底层的复杂机制中。简单来说,就是读写两端的上下文(Context)互相踩踏了:
Context 直接覆盖了读端的 Context,导致读端彻底失联。Waker 必须实质性永久存储。几轮 Prompt 迭代下来,Opus 4.6 依然在原地打转,给不出有效代码。眼看它陷入死胡同,我只好叫停,人工接管了修复工作。修复逻辑其实很清晰(见PR#424):将读端 Waker 的存储条件放宽为任意条件,并让写端的读操作从 poll 接口全面转向 try_recv。
随后我让 Opus 补充测试。但哪怕只是修个用例,它又折腾了两个多小时——一直查不出测试为何不稳定。直到最后,opus 把多个 tokio multi-thread runtime 改成 current thread runtime,测试才最终稳定下来。
带着修复好的 tentacle 代码,我回到 fiber 层,让 GPT4.5 尝试运行,看测试能否顺利通过。
很遗憾,测试依旧全挂。于是,我交给它两个任务:确认 tentacle 修复是否生效,并找出当前问题到底出在哪。
在排查过程中,LLM 暴露出一种极强的**“局部最优解”执念**(或者说“打补丁”倾向)。它非常固执地想在 fiber 业务层通过重复发消息、或者魔改发消息流程来掩盖问题,而不愿意下沉到协议栈底层去拔除病根。
在我的反复纠偏、强制要求它“去找核心机制”后,GPT5.4 终于开窍,通过自动注入 log 追踪到了死锁的真凶:消息其实已经到达,但被卡在了 codec 编解码器里。
GPT 敏锐地指出,由于 codec 实现中 is_readable 初始状态默认为 false,单靠替换 read_buffer 无济于事。唯有等待第二个消息到达,触发 is_readable 翻转为 true,系统才会去解析 buffer 里暂存的首条数据。
顺着这个思路,我换了一种方式使用 split(见PR#425),保持其 is_readable 状态为 true,让 codec 能立刻去 buffer 里解析暂存的数据,彻底解决这次优化暴露出来的问题。
回顾这场漫长的 debug,重现稳定的测试环境、分析庞杂的日志、找到核心异常现象,这些脏活累活全是 LLM 解决的。它大幅降低了我处理复杂环境(尤其是 fiber 环境重现)的难度。当你摸不着头脑时,LLM 绝对是不知疲倦的试错机器,能从各个方向快速探路。
但真正拍板并解决问题的,依旧是开发者自己。LLM 在面对深水区 Bug 时,思维容易陷入局限,倾向于在上层“修修补补”交差。
作为“主刀医生”,你必须保持清醒的架构视野,不断通过 Prompt 纠正它的探索方向,逼迫它下沉到底层寻找病根。
与 LLM 一起 Debug,不是把大脑外包给它,而是拥有了一个极其强大的感官放大器。你指引方向,它扫清迷雾。
请登录后评论
评论区
加载更多