parking_lot: ffffffffffffffff...

这是一篇关于 Fly.io 公司 Anycast 路由器中一个严重并发 bug 的文章,主要内容如下:

  • 背景:Fly.io 是一个在全球多个地点运行应用的公共云,其 Anycast 路由器是代码库中最大的 Rust 项目。客户给 Fly.io 提供 Docker 容器,并指定在全球 30 多个区域中的一个运行。Fly.io 将容器转换为轻量级虚拟机,然后将它们链接到 Anycast 网络,fly-proxy负责路由请求。
  • 角色介绍

    • fly-proxy:无畏的 Anycast 路由器。
    • corrosion:无畏的 Anycast 路由协议。
    • Rust:一种编程语言。
    • read-write locks:允许多个读取器或一个写入器的同步原语。
    • parking_lot:Rust 中备受推崇的优化锁实现。
  • Anycast 路由fly-proxy在处理请求时做了一些有趣的事情,它是用 Rust 编写的,处理多个协议、TLS 终止和证书颁发等。管理数百万个应用的数百万个连接是一个难题,即“状态分布问题”,这类似于一个路由协议。
  • 路由协议实现困难:全局复制的 SQLite 数据库是一个很好的原始数据,但每次请求到达时并不实际进行 SQL 查询。fly-proxy中有一个用于路由信息的记录系统(Corrosion)和一个用于快速决策的内存聚合(Catalog)。去年出现了一个有趣的 bug,if let表达式中的锁持有时间过长,导致整个 Anycast 路由层出现死锁。
  • Watchdog 与区域化:Anycast 中断的经历促使 Fly.io 制定了两项任务,一是通过“看门狗”系统使死锁不再致命;二是解决所有路由状态共享一个全局广播域的问题,即“区域化”,将大多数更新限制在发生的区域。
  • 新 bug 及解决过程

    • 新 bug 第一轮:懒加载:将原始的 Anycast 路由器转换为具有分区状态的区域化路由器,部分fly-proxy目录开始懒加载,之后代理开始锁定并被看门狗杀死,回滚了更改,怀疑是懒加载改变了读写模式和锁竞争。
    • 新 bug 第二轮:锁重构:对代理进行一些重构,包括使目录写锁超时、消除 RAII 式锁获取并替换为显式闭包以及添加日志和指标,但仍出现更多锁定问题,尤其是在欧洲。
    • 新 bug 第三轮:遥测检查:通过仪器检测仍发现死锁,线索是锁超时日志在代理锁定和被杀死之前出现,但无法确定原因。
    • 新 bug 第四轮:陷入疯狂:看门狗杀死代理时会捕获核心转储,发现代理锁定时没有线程在关键部分,但有多个线程等待获取锁,推测可能是内存损坏,但内存看起来并未损坏,通过gdb进行更深入的核心转储检查。
    • 新 bug 第五轮:绝望之举:切换到read_recursive,生成了新的证据RwLock 读取器计数溢出日志消息,但问题仍未解决。
    • 最终发现:经过一系列排查,发现parking_lot的 RWLock 实现存在问题,在特定条件下会导致锁损坏。parking_lot团队很快确认并修复了该 bug,修复后锁定问题不再出现。
  • 后续改进:审计了所有目录锁定,消除了iflets,停止依赖 RAII 锁保护,获得了锁定时指标,所有写入都有标签并记录慢写入的上下文信息,还跟踪写入锁的最后持有者和当前持有者,以便下次出现死锁时能快速定位问题。

总之,通过一系列排查和修复工作,Fly.io 解决了这个严重的并发 bug,并在过程中进行了一些改进,以更好地处理未来的并发问题。

阅读 12
0 条评论