BUG 场景
《写作天下》新出了名片库功能,所有的操作都在侧边栏中,包括添加名片、修改名片等。
在侧边栏的按钮中添加名片、双击卡片弹出编辑窗口。
为了方便,将之转移到了编辑框中,在编辑框的菜单中弹出名片操作。
本以为只是极度简单的添加菜单、信号槽连接,第一次测试并没有问题,心中侥幸,但在接下来的使用过程中却出现了偶尔的卡死崩溃问题。
BUG 复现
编辑框的右键菜单中添加名片,通过信号槽连接到主窗口,弹出名片编辑弹窗。实测有几率崩溃。
之后重复了上百遍的尝试添加打开弹窗,然而只有3次崩溃,其余并没有任何问题。
换了个思路,点击“创建名片”按钮,并无异样,名片也顺利创建了。然而在第二次点击创建的时候,程序直接卡死,崩溃。
找到了百分百复现的地方,接下来就开始了探索。
BUG 探索
从“添加名片”的按钮点击事件开始,打LOG。
1 | void CardEditor::slotCreateCard() // “创建”按钮的槽 |
因为行数较少,所以在每一小步的前后都加上log。
接着一步步深入,这是信号槽顺序:
名片编辑窗口.创建名片 >> 名片库.添加名片 >> 名片库.触发添加信号 >> 名片库管理器.接收槽 >> 名片列表控件管理器.接收槽 >> 2个名片列表.添加新名片 >> 名片列表控件管理器.触发刷新章节名片信号 >> 开线程异步刷新 >> 刷新当前章节的名片 >> 刷新章节名片列表控件 >> 关闭名片编辑窗口
这么多文件?一步步深入,写了几十行的log代码,打印出上百行。
按钮 >> 名片库 >> 名片库管理器 >> 名片控件列表管理器 >> 名片控件列表 >> 章节编辑器,主要就是这四个类。
名片库 Cardlib
1 | void Cardlib::addCard(CardBean *card) |
名片库管理器 CardlibManager
1 | connect(cardlib, &Cardlib::signalCardAppened, [=](CardBean *card) { |
名片控件列表管理器 CardlibGroup
1 | // 名片库添加名片时,添加到卡片列表末尾,并且聚焦此卡片 |
名片控件,由于之前在侧边栏的操作无异常,所以只简要测试。
章节编辑器 ChapterEditor
1 | connect(&gd->clm, &CardlibManager::signalRehighlight, [=] { |
查看日志,最后卡在了 editor.highlighter begin
这一行,说明是 highlighter 这一行出问题了,高亮显示名片内容时崩溃。
进入 highlighter,重绘高亮的 highlightBlock 的方法中在名片部分前后打上 log,输出,发现有头有尾,应该是正常运行,无法发现在哪里停止运行。
1 | log("highlightBlock begin"); |
甚至注释掉名片部分,也无法阻止在第二次创建名片的时候停止运行。
日志是这样的:
1 | clm.signalRehighlight |
问题肯定就出在了 highlighter->rehighlight();
这一行。将它延时100毫秒:
1 | connect(&gd->clm, &CardlibManager::signalRehighlight, [=] { |
继续崩溃。
注释掉 highlighter->rehighlight();
,这下子倒是没有问题了,但是也彻底丧失了高亮的功能,这是我不想的,日志如下:
1 | clm.signalRehighlight |
思考为什么会导致崩溃。最可能的原因就是空指针了。
再研究日志,通过全局的名片库管理器发送的信号,只发送了一次,但是为什么接收到了4次,即高亮四次?
这才想到,目前一共打开了2个能接收信号的自定义编辑框,分别是章节、大纲,但还有两次呢?
突然意识过来,名片编辑窗口中,显示名片自己信息的编辑框,就是和章节编辑一模一样的自定义编辑框!而且还是两个!这就解释了为什么会多出两个接收的槽。
BUG 原因
名片编辑窗口,为了显示简介和详情的高亮,它内部本身就用了略微修改的章节编辑框ChapterEditor
类,由其派生出一个通用的GeneralEditor
,后期可添加适配多个场景,例如大纲。而在构造函数中,连接了全局的名片管理器,统一刷新名片高亮。
除了两个特殊的编辑框,还有一个“创建名片”按钮,点击后创建名片,并且关闭弹窗本身。为了防止内存溢出,又设置了自动 delete 的属性setAttribute(Qt::WA_DeleteOnClose);
。创建名片之后,立即关闭自己,触发delete,同时由于控件的父子关系,自动析构子对象,包括两个正在刷新名片高亮的编辑框。
原因也正是如此,可能 Qt 在高亮时没有阻塞主线程,正在高亮的同时又 delete 掉编辑框控件,导致没有高亮的对象,野指针导致停止运行。
BUG 解决
自定义编辑框连接信号槽时,判断编辑的对象类型,如果是名片编辑窗口的那两个,则不刷新高亮。
1 | if (!editing.isBrief() && !editing.isDetail()) // 如果不是名片的简介和详情 |
这样的唯一问题,就是在编辑某一个名片时,这个窗口内部并不会立即修改高亮,而是需要等待后期刷新。
BUG 总结
多线程关键词高亮,嗯,这个我真没想到。
还有编辑框析构后没有删除控制高亮的对象,也没有想到。