多线程fork,子进程执行哪些函数是安全的?

我们最近正好遭遇了 fork-unsafe 的 localtime/localtime_r,在这里跟大家分享一下:

(一)背景

MyTopling 是基于 ToplingDBMySQL,分叉自 MyRocks,ToplingDB 则分叉自 RocksDB,兼容 RocksDB 接口,从而 MyTopling 可以复用 MyRocks 的大部分成果。

ToplingDB 早已开源,MyTopling 也将于近期开源。

(二)现象:分布式 Compact 超时

大家可以先参考《MyTopling 分布式 Compact(一):从多线程到多进程》,然后接着看:

在我们的测试中,MyTopling 的分布式 Compact Worker 一直有一个问题:经常跑着跑着,Compact Worker 就假死了,这个问题之前在多线程模型中也偶尔出现,当时经过痛苦的排错定位,最后发现是 NFS (客户端) 超时引起的,这个概率虽然很低,但有时会严重到接近卡死。

既然是 NFS 问题,所以我们除了骂娘也无能为力,不过好在 Compact Worker 是无状态计算,这个问题反映到 DB 端只是一个分布式 Compact 任务超时,换一个结点重试即可。

后来在多进程模型中,碰到假死我们就直接重启进程。直到昨天晚上,我忽然意识到,以前多线程的时候,假死的频率很低,而现在多进程,跑上几个小时,几乎必然假死……

(三)重新分析

在多线程模型下,Debug 比较容易,直接挂上 gdb 就行,但是在多进程模型下,每个 Compact Job 都会 fork 出一个子进程,并且这个子进程很快就执行结束了。所以我们 Debug 的时候,将其设为多线程模型,因为不管是多进程还是多线程,代码都是复用的,当然用更省力的方式 Debug。

但是现在我们是要定位为何多进程时假死的概率要高出这么多,那就必须按多进程 Debug……

把 Compact Worker 集群限制到仅一个结点,打高压力,跑了六个多小时,假死得凉凉的!开始用 gdb 逐个查看进程,然后惊奇地发现,所有的 Compact Job 进程都卡在 __tz_convert 中:

而我们的 StrDateTimeNow 并没有调用 __tz_convert,然后发现,__tz_convert 是在 localtime 函数中调用的……

显而易见,__lll_lock_wait_private 是等在 mutex 上了,定位到这里,一下就知道原因了,几乎所有的最佳实践都强烈反对混用多线程和多进程,而我们这里的父进程偏偏就是多线程,于是在 fork 子进程时,意外就发生了,在同一时刻,发生了两件事情:

  1. 父进程线程 1 在 __tz_convert 中拿到了 mutex lock
  2. 父进程的线程 2 执行 fork 产生子进程 A

因为 fork 在语义上是父进程的一个拷贝,所以在子进程 A 中,__tz_convert 中的那个 mutex 是处于 lock 状态的。在父进程中,线程 1 在 __tz_convert 中拿到 mutex lock,干完事情就会 unlock,然而子进程 A 中却并没有父进程中的线程 1,所以那个 mutex 就永远处于 lock 状态……

(四)解决问题

骂 glibc 的娘是必不可少的:有无数种方法可以避免 localtime 这样的函数使用全局锁,但它偏不!

骂娘归骂娘,问题是必须要解决的,稍微搜索一下就发现,不止我们碰到了该问题(关键词 localtime "fork"),我们复用了 redis 的解决方案,稍作修改之后,一并给上游 RocksDB 发了个 Pull Request #10652

(五)讨论

最佳实践都说不要混用多进程与多线程,但是现实世界不是乌托邦,面对某些问题,我们必须采取非常手段,自然也就容易掉坑。就这个坑而言,glibc 甚至 posix 标准有义务把它填平,最低限度,也得提供一个内部无锁的 localtime 版本。

最后,大家可以讨论一下:这到底是个 Bug,还是个 Feature 呢?

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值