Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

多线程并发冲突导致崩溃 #1487

Closed
3 tasks done
wzv5 opened this issue Jan 31, 2025 · 29 comments
Closed
3 tasks done

多线程并发冲突导致崩溃 #1487

wzv5 opened this issue Jan 31, 2025 · 29 comments
Labels

Comments

@wzv5
Copy link
Contributor

wzv5 commented Jan 31, 2025

上报前请检查

  • 我遇到的问题没有其他人在 issue 里提到过
  • 我的小狼毫版本于 rime/weasel 下载
  • 我在使用小狼毫的最新发布版本,或最新发布版本后的 CI 构建

操作系统信息

  • OS 详细版本: win11 24h2
  • 小狼毫版本: 0.16.3

描述遇到的问题
发现存在多个 PipeServer::_ProcessPipeThread 线程,并发调用 librime,但 librime 并不支持多线程并发调用。

复现步骤
无,不定时出现。

用户文件
weasel_dmp.zip
压缩包内包含 dmp 文件、我修改的部分代码、我编译的 rime.dll 及其 pdb。

其他补充说明
WeaselServer 一直出现不定时崩溃,可能一天一次或多次崩溃,频率并不高。
起初怀疑是 lua 插件的问题,因为崩溃位置总是在 lua 插件中。
但在排查中发现,崩溃时 lua 环境已经乱套了,也许并不是 lua 的问题,而是来自更上层的代码。
查看 librime 代码发现,librime 并不支持多线程并发调用,于是怀疑是存在并发调用导致的崩溃。
在 librime 中加入了一些并发调用检查后,果然抓到了并发调用。

@wzv5 wzv5 added the Bug label Jan 31, 2025
@mirtlebot
Copy link

好奇到底是怎么复现出来这种不定时的崩溃,似乎总是在新开会话(开机,打开应用,新建窗口)的时候。

@wzv5
Copy link
Contributor Author

wzv5 commented Feb 1, 2025

好奇到底是怎么复现出来这种不定时的崩溃,似乎总是在新开会话(开机,打开应用,新建窗口)的时候。

好像都是在切换前台窗口时崩溃,前一个窗口在收尾,新窗口在初始化,两个窗口对应两个session,每个session都有各自的_ProcessPipeThread线程,然后两个线程就可能出现并发调用,就崩了。
而之所以崩溃位置在lua插件中,是因为lua插件正好做了严格的错误检查,检查到问题后主动abort了,但真正的问题并不在lua插件中,一直以来都被误导了。

@fxliang
Copy link
Contributor

fxliang commented Feb 1, 2025

librime是不是禁止多线程不确定,隐约记得lua多线程应该是要手动改点东西

另外记得leveldb似乎是限制线程?

验证这个多线程相关的事情可以试试手工改下rime api console,加上多线程不停的创建和关闭session,中间加个模拟按键什么的之类潜在可能导致崩溃的业务,带lua和不带lua插件分别做循环多次多线程测试,或者有更好的判断

@wzv5
Copy link
Contributor Author

wzv5 commented Feb 1, 2025

librime是不是禁止多线程不确定

至少在代码层面,是不支持多线程的,完全没做保护。
比如与session相关的这些调用,存储session的容器是一个普通的map,还没加锁,这种情况多线程调用必然会崩的。

@fxliang
Copy link
Contributor

fxliang commented Feb 1, 2025

好像都是在切换前台窗口时崩溃,前一个窗口在收尾,新窗口在初始化,两个窗口对应两个session,每个session都有各自的_ProcessPipeThread线程,然后两个线程就可能出现并发调用,就崩了。

小狼毫是前后端分离的结构,前端窗口并不持有librime,而是始终通过唯一的WeaselServer来操作librime,前后端之间标识窗口的是ipc id,在WeaselServer通过map映射到librime中的sessionid。sessionid 的本质是指针地址。WeaselServer会持有多个sessionid,而不是每个session持有_ProcessPipeThread线程。

反证法的思路是,我也有遇到崩,关掉lua插件配置使用之后一次都没有复现了。我另外写的rime.toy并不存在多线程,一样会有机会死在lua插件上,当然我那个只有一个session没有多次的创建session的操作。

所以建议还是写测试代码再看。

@wzv5
Copy link
Contributor Author

wzv5 commented Feb 1, 2025

前后端之间标识窗口的是ipc id,在WeaselServer通过map映射到librime中的sessionid

我知道ipc和session的区别,只是为了方便解释才统一说成session,但一个ipc或者client对应一个线程是没问题的。

反证法的思路是,我也有遇到崩,关掉lua插件配置使用之后一次都没有复现了

我上面已经解释过,之所以崩溃位置在lua,是因为lua做了错误检查,主动abort了。注意,对于多线程并发操作来说,并不一定会崩溃,轻则只是数据混乱,当然,数据混乱就更难排查了。

我另外写的rime.toy并不存在多线程,一样会有机会死在lua插件上,当然我那个只有一个session没有多次的创建session的操作。

对于lua本身的问题,我也排查了,比如Memory确实存在问题,需要在lua层主动gc才能释放,但这跟当前说的问题无关,不是同一个问题。

所以建议还是写测试代码再看。

我已经尝试在librime中对session map进行多线程加锁,现在运行一天了,没有崩溃,之后会再观察看看。当然需要加锁的地方肯定不止这一处,只是session map更容易被并发修改。

@changzaicl
Copy link

@wzv5 能不能share一下,我也帮助测试崩溃情况。谢谢!

@wzv5
Copy link
Contributor Author

wzv5 commented Feb 1, 2025

@changzaicl 因为只对session map加了锁,所以还是可能出现多线程冲突的,只能说崩溃的几率大大降低了,如果真想试试的话。。

rime.zip

压缩包内包含修改的源码和pdb。

之前懒得折腾weasel编译环境,刚刚给搞了一下,所以直接从WeaselServer下手了,处理更容易,这也是我为什么要在这里提issue而不是在librime仓库,librime本身就没考虑多线程,从librime下手难度颇高。

修改WeaselServer就容易多了,暴力加锁:

Image

当然这仅供测试,加锁范围有点太大了,不太好。

下载这个测试:

output.zip

rime.dll还用原版就行。

@changzaicl
Copy link

谢谢,我试试是否会少崩溃

@wzv5
Copy link
Contributor Author

wzv5 commented Feb 4, 2025

经过这3天的测试,应该可以确定,至少我电脑上的崩溃就是因为多线程并发冲突。之前每天都会崩溃数次,为WeaselServer加锁后,3天没有出现任何崩溃。

如果你也遇到算法服务日常崩溃,且崩溃位置在lua插件中,不妨来试试我修改后的算法服务,看看能否解决。下载地址见我之前的回复,只要替换 WeaselServer.exe 就行了。

至于如何解决多线程并发冲突,最简单的方法就是像我上面发的那样,为 HandlePipeMessage 加锁,两行代码搞定。

最后补充一下我使用的配置:

我只能说在我这套配置下,lua插件是替weasel背锅了,如果你用了其他lua脚本,请自行检查脚本有没有问题。

@wzv5 wzv5 changed the title 也许是多线程并发调用导致的崩溃 多线程并发冲突导致崩溃 Feb 4, 2025
@mirtlebot
Copy link

@lotem 公子抽空看看,似乎是历史代码,有没有可能是小狼毫 0.14.3 崩到 0.16.3 的主因。

@lotem
Copy link
Member

lotem commented Feb 4, 2025

我不懂啊。
加鎖沒問題。但是最好能搞明白,
多線程是哪裏來的?

@wzv5
Copy link
Contributor Author

wzv5 commented Feb 4, 2025

@lotem 多线程在这里创建的:

boost::thread th(
[&handler, pipe, this] { _ProcessPipeThread(pipe, handler); });

@lotem
Copy link
Member

lotem commented Feb 4, 2025

handler(msg, [this, pipe](Msg resp) { _Send(pipe, resp); });

這裏是 PipeServer::_ProcessPipeThread;是不是應該在這個事件循環裏加鎖呢?
_Receive 從 pipe 讀數據,還好;
在 handler 的回調裏,_Send 向 pipe 寫數據,還會調 _Reconnect

@wzv5
Copy link
Contributor Author

wzv5 commented Feb 4, 2025

应该对librime api的调用加锁,跟pipe本身没啥关系,读写pipe可以多线程。
在 HandlePipeMessage 中加锁是最方便的,保证同时只有一个线程调用librime api就好。
其实如果用IOCP改造PipeServer的话,就完全不需要创建多线程,自然也就不需要加锁,但这就比较麻烦了。我之前尝试了一下,PipeServer的代码分散在几个项目中,部分代码还跟client共享,不太好改。

@lotem
Copy link
Member

lotem commented Feb 4, 2025

寫入 pipe 能多線程嗎?會寫亂吧。

@wzv5
Copy link
Contributor Author

wzv5 commented Feb 5, 2025

多线程读写pipe没问题的,每个线程持有各自独立的pipe,互不影响。

@lotem
Copy link
Member

lotem commented Feb 5, 2025

哦。改成管道了。
我剛開始寫的時候是用 Windows 消息加共享內存。
我現在懂了。沒問題了。

@fxliang 老師你怎麼看。

@changzaicl
Copy link

高强度码字(第1天上班),同步,多开几个Lua等使用了一天,的确没有错误发生。

@fxliang
Copy link
Contributor

fxliang commented Feb 5, 2025

哦。改成管道了。 我剛開始寫的時候是用 Windows 消息加共享內存。 我現在懂了。沒問題了。

@fxliang 老師你怎麼看。

不懂。

从一个不懂的角度我只能试了下termux下修改rime_api_console来跑下thread似乎是有较大概率会崩

已知的较多崩的场景(切换应用或新开应用),前后两个应用tsf里会调到STDMETHODIMP WeaselTSF::OnSetThreadFocus()STDMETHODIMP WeaselTSF::OnKillThreadFocus() ,不知道锁或者其他方式有没有副作用影响到RimeWithWeasel里的激活session id之类的

lotem added a commit to lotem/weasel that referenced this issue Feb 6, 2025
@lotem
Copy link
Member

lotem commented Feb 6, 2025

IPC 部分代碼好複雜呀!
就按 @wzv5 的改法做吧。安全第一。
稍稍修改一下行不行:
2b84922

lotem added a commit to lotem/weasel that referenced this issue Feb 6, 2025
@fxliang
Copy link
Contributor

fxliang commented Feb 6, 2025

应该可以,反正就算真有什么问题也容易改回来🐶

@lotem
Copy link
Member

lotem commented Feb 6, 2025

那我先提交上去咯?
編譯通過了沒測試,不知道中不中用。

@lotem lotem closed this as completed in 2dc4e19 Feb 6, 2025
@fxliang
Copy link
Contributor

fxliang commented Feb 6, 2025

那我先提交上去咯? 編譯通過了沒測試,不知道中不中用。

可能潜在关联的issues #1478 #1429 #1425 #1417 #1408 #1420 要是这些都有人反馈关闭了,考虑发个小版本这样吧

@mirtlebot
Copy link

还是外来的和尚会念经,大概是解决了数年来无故崩溃的疑难杂症了。

@mirtlecn
Copy link
Contributor

mirtlecn commented Feb 9, 2025

请相关人员关注,在这次更新后(当下在 CI 版本 Release),小狼毫,因崩溃而卡顿的现象有无解决:

iDvel/rime-ice#1135 changzaicl 反馈再无崩溃
iDvel/rime-ice#876 @expoli
iDvel/rime-ice#1150
Mintimate/oh-my-rime#171 @Mintimate
gaboolic/rime-frost#141 @baendlorel
gaboolic/rime-frost#140 @0x273c42
rime/home#1727 @WingFoxie
hchunhui/librime-lua#349

显性特征为:

  • 窗口卡顿数秒,并且
  • 常常出现在切换 & 新开窗口阶段,并且
  • 小狼毫崩溃,日志目录下有 dmp 文件,并且
  • 日志可能存在 lua abort 而产生的 error 信息

日常出词慢、候选框出现慢(无 dmp 文件),和特定框架程序下的崩溃(如 Tauri),特定程序下(如部分 QT 框架)的出词慢不属于此列

@yyh1wps
Copy link

yyh1wps commented Feb 12, 2025

感動無語凝咽
我未作任意個性化與lua, 開箱即用卻困頓於此, 一度疑慮反思自身環境問題
換用此 build 再無擾人之惱
Image

@wzv5
Copy link
Contributor Author

wzv5 commented Feb 12, 2025

感動無語凝咽 我未作任意個性化與lua, 開箱即用卻困頓於此, 一度疑慮反思自身環境問題 換用此 build 再無擾人之惱

哈哈,有这么夸张么,问题能解决就好。

@YummyCocoa
Copy link

请相关人员关注,在这次更新后(当下在 CI 版本 Release),小狼毫,因崩溃而卡顿的现象有无解决:

iDvel/rime-ice#1135 changzaicl 反馈再无崩溃 iDvel/rime-ice#876 @expoli iDvel/rime-ice#1150 Mintimate/oh-my-rime#171 @Mintimate gaboolic/rime-frost#141 @baendlorel gaboolic/rime-frost#140 @0x273c42 rime/home#1727 @WingFoxie hchunhui/librime-lua#349

显性特征为:

  • 窗口卡顿数秒,并且
  • 常常出现在切换 & 新开窗口阶段,并且
  • 小狼毫崩溃,日志目录下有 dmp 文件,并且
  • 日志可能存在 lua abort 而产生的 error 信息

日常出词慢、候选框出现慢(无 dmp 文件),和特定框架程序下的崩溃(如 Tauri),特定程序下(如部分 QT 框架)的出词慢不属于此列

感谢提醒,我最近在 Windows 虚拟机里测试了一下 CI 版本的小狼毫,是没有发现卡顿问题。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

8 participants