旋钮"装死"事件:一次按键丢失问题的完整复盘
一、案发现场
前段时间接了个 OpenHarmony 的活,负责 MCU 到应用层的数据链路。这设备的交互门面是旋钮:设密码、调设置、翻菜单,全靠它拧。用户拧旋钮的心态跟关冰箱门一样——拧了就该有反应。
结果测试反馈:旋钮偶尔"装死"。密码页拧了数字不动,设置页拧一格要等半秒,运气差的时候彻底没反应。硬件没坏,映射没写错,问题出在我最熟的那条链路上,这就很尴尬了。
二、日志不会说谎
翻日志,一眼看到这行:
[poll] submit status=-8 drops=401
丢失计数攒了 401 个,比我健身打卡勤快多了。顺着这条线往下挖,案情逐渐清晰。
背景是这样的:为了统一按键来源,我们把旋钮按键从"专用字段"迁到了一个"公共命令字段"——它既承载按键码,也承载各种业务命令(投屏、导航、恢复出厂……),相当于把专用车道改成了客货混装。
于是两个老雷被同时踩响:
其一,挤窗口。 字段轮询之后要过一个单槽提交口才能进数据中心。慢组一轮 150 多个字段,处理耗时比按键的轮询周期还长。按键码赶到时窗口正忙,返回 BUSY,直接拒收。
其二,撕底单。 旧逻辑更绝:读到按键后先把寄存器清 0,再提交。等于快递还没签收,先把底单撕了。提交一旦失败,下一拍重采样读到的自然是 0——按键永久消失,连尸体都不留。
这两个坑其实一直都在,只是以前不挤、不堵,从没被踩响。重构没有引入新 bug,只是把旧地基的裂缝暴露了出来。
三、手术分了三刀
第一刀,止血。 先上个过渡 PR:给按键单独开一个提交槽,不再和慢组一百多个字段挤同一个窗口;清 0 的时机也改成"入队成功之后"。丢键立刻止住,drops 不再增长。但这版按键还得路过业务引擎,陪跑一遍两百多条映射规则——病治了,走路还是瘸。
第二刀,正骨。 根治术来了:轮询读到按键码先过白名单,命中就直接分发给 UI 输入队列,不进业务引擎、不跑映射表;业务码照走原链路,但改成"隔拍补 0"——订阅方必须看到完整的"有命令→归零"两个沿,要是 N 和 0 挤进同一次通知,它们读到的永远是 0,通知等于白发。提交失败的批次也不再整组重采样,只失效自己的缓存,谁的孩子谁抱走。
第三刀,复健。 丢键治好了,该聊手感了:快组采样周期从 30ms 压到 10ms,按键消费搬进 UI 线程,16ms 一拍内就地消化,整条链路少两次任务切换。旋钮从"偶尔装死"进化成"指哪打哪"。
至于设置页"慢半拍",查实是另一桩案子——JPEG 解码加整页重绘,跟按键链路无关。丢键案结案,卡顿案移交渲染组,别把两个罪名安在一个嫌疑人头上。
四、复盘几句话
- 事件和状态别混一条通道。 状态丢了可以等下一拍,事件丢了就是丢了,处置方式天然不同。
- "成功后清零"是事件字段的铁律。 失败不清零、重试保留现场;顺序反了,就是自己动手毁尸灭迹。
- 手术要分刀切。 三个 PR 各管一段,都能独立验证、单独回退。先止血再正骨,评审的人也谢你。
- 先看数据再动手。 一行 drops=401 的日志,比三天盲猜值钱。
老代码的 bug 不会因为没人踩就消失,它们只是在安静地等下一次重构。
更多推荐
所有评论(0)