一次艰难的 cpython issue 排查过程,以及我学到了什么
👍
18
🙌
引子
最近依然在尝试做一些大模型目前做不到的事儿,去满足一些存在感。但是 TiDB 的 issues 没有以前多了,恰好某一天 @kemingy 发现 cpython 源码里有些写的不好的地方,我想 cpython 也许也可以?但我万万没想到一个 patch 进入到 cpython 是多么的难,我已经前前后后修了 4 个 cpython 的 bug 但是每个都在过程中,过程中学到了不少东西,但更开心的是认识到了目前即使最强的大模型的局限。
bug ?
- bug 是这个 https://github.com/python/cpython/issues/134163
尝试的过程
下面是我尝试的过程,如果能同样帮到喜欢 bug 的朋友就更好了
- 当然是尝试复现,3.13 在 repl 使用 issue 中的语句 hang, 3.14 不 hang 住,是 3.13 only 的问题
- Claude Code 启动,先让大模型帮我定位下,大模型前前后后走了几圈,都是错的,而且大模型的思路有问题,它认为 3.14 修了就是有的 commit 修了,再不停的查 commit
- 自己来吧,首先先缩小范围
- 在 repl 里必复现
- 直接使用 ./python xxx.py 没事
- pty 没事儿
- subprocess 没事
- 卧槽只有 repl 里?
- 强制使用旧 repl
PYTHON_BASIC_REPL=1 ./python
也没事儿!
- OK 那是 new repl only, 定位好了问题
能复现就是成功的一半?
- no 这个 issue 并不是
- 这个是 c 层面的还是 python 层面的?因为只有在 new repl 里我开始以为是 python 层面的
- 在所有可能是 memory error 的地方接上 except: nothing work
- 在源码里加 print 卧槽加上 print bug 消失了,妈的
- 因为上条我怀疑我走错路了,大概率是 c 层面的,但是我得定位到哪一行造成 c 层面的错误
- 再一顿 print debug 之后发现是 console.py 里的这一行
exec(code, self.locals)
- OK 找到这个 bug 就可以简化为在 new repl 里执行
exec("_testcapi.set_nomemory(0)")
但是但是
- 我在 c 层面一路找 memroy 相关的没头绪,能改的地方都改了
- 过程中还熟悉了一点 cpython 的代码
- 但是还是不行
先放弃了,但还想着
- 然后我就把问题先搁置了去忙别的了
- 但是跑步的过程中突然有了一点想法也许不是 3.13 only 的
- git branch 启动!对不是!发现 3.14 第一个版本仍然有这个 bug
- 那么是某个小 branch 给修了
- branch 二分法启动!最后发现是 3.14a2 -> 3.14a3 之间某个 commit 给意外的修复了?
- 既然 branch 能二分法我突然悟到了可以 bisect
- 第一次用 bisect 在大模型的教学下一路找(这里有个坑就是 good 和 bad 是反的)最后发现是这个 commit 意外的给修了 5fc6bb2754a25157575efc0b37da78c629fea46e
- 修的人好强啊
- 能 cherry-pick 么?不能!因为改动太大了这之间的里
- 能借住他修的一部分修复么?一路尝试,是不能的,因为本质上不是一个问题
- 又尝试了好多。。。也不行,确性了 cpython 遗留的 bug 都是有难度的
找朋友吧
- 当然是先找@kemingy 帮忙,他比我多走了两步但是也卡住了
- 找 @frostming 也试试
- 暂时没结果,早上突然想到我要去 pycon 的机票还在拖延,不想麻烦 gray 帮忙了 ,等下 @graymon 的演讲是 gdb 相关也许可以试试
- gdb 启动
- 这里也有个坑好像 gdb 会改变内存
- 但是最后还是找到了是在 /Python/ceval.c b/Python/ceval.c 的 goto exception_unwind; 里陷入了无限的递归
- 修复如果递归直接 pyerr clear 卧槽不 hang 了但是是 Segmentation fault
- 继续,成功了!于是有了如下修复
- 感谢朋友们的帮忙,开心
修复
diff --git a/Python/ceval.c b/Python/ceval.c
index 8c0cb29863c..3fe97a2c74c 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -912,7 +912,13 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
int frame_lasti = _PyInterpreterFrame_LASTI(frame);
PyObject *lasti = PyLong_FromLong(frame_lasti);
if (lasti == NULL) {
- goto exception_unwind;
+ // If we can't allocate memory for lasti during exception handling,
+ // this likely means we're in a severe memory shortage situation.
+ // Instead of going back to exception_unwind (which would cause
+ // infinite recursion), directly exit to let the original exception
+ // propagate up and hopefully be handled at a higher level.
+ _PyFrame_SetStackPointer(frame, stack_pointer);
+ goto exit_unwind;
}
PUSH(lasti);
}
修复的对么?
- 不知道,先跑测试 3.13 都通过了
- main 有这个问题么?没有,代码结构都改了
- 对不对我先把过程分享出来,能解决一大部分也是开心的
感想
- 谢谢朋友们
- 大模型还是有局限的但还是能帮不少忙的
- gray 好厉害
- cpython 能遗留下来的 bug 都非常难
- 还是开心
---- update ----
https://t.me/c/1459082815/900
和 gray 学习。
太厉害了!已经开始修 cpy 了! 所以大模型一开始的思路也没错,就是找 commit 。只是没找到🤣
学到很多 向大佬学习
应该是找不到的,那个属于意外的修了
你可以重点看最下面的链接 gray 特别特别厉害
很久没有在跑步的时候灵光乍现了, 哈哈哈
git bisect
也可以用new和old标记学习了!