记一次 Android 周期性句柄泄漏的排查

news2024/11/24 11:58:06

滴滴国际化外卖 Android 商户端正常迭代版本过程中,新版本发布并且线上稳定一段时间后,突然触发线上 Crash 报警。

97cfe016ee604451f87e7298db909024.png

第一次排查发现是在依赖的底层平台 so 库中崩溃,经过沟通了解到其之前也存在过崩溃问题,所以升级相关底层 so 版本。重新发版后短期没有出现 Crash 大面积上报情况,只有零星上报,但不久后又发生了第二次大面积 Crash 上报。具体信息如下图所示:

09ef9b5b54902d7772da4703f771775a.png

在定位分析问题的过程中收获很多,通过这篇文章分享该 Crash 的排查过程、问题根因以及一些经验总结,希望能为读者在遇到同类型问题时提供一些参考。    

排查流程

Crash 描述

Crash 的量级,从两次高峰期的峰值来看,集中爆发的峰值为每日50次左右,第二次爆发的峰值最高为173次。同时,Crash 和影响用户数量是一比一。

4584850639a2ca2b0f1ef239fc8fbce7.png

两次大面积爆发的问题设备集中在华为的三款机型,运行时间都在14天左右,内存情况正常,线程情况正常,此时还没有相关线索指向句柄,所以这个时候没有关注句柄,如下图:

e794afb2ee3854dff69ec3031ad7d959.png

098dd452d3f401e441b1c6a0e5b1510e.png

定位分析

从整体的 Crash 描述以及 Crash 统计平台的相关数据来分析,每隔14天就大面积爆发一次,可以确定是周期性问题,这种问题的排查难度较高。

根据上报的错误日志,明确其崩溃位置是在底层的 libpush.so 库,同时和其维护的同学沟通后发现依赖的 so 版本出现过问题,所以我们在第一次大面积上报之后,升级了底层 so 库版本。虽然当时增发版本后没有明显的 Crash 上报,但还是存在零星的 Crash 上报,这让我们放松了警惕,未对问题的根因进行定位,才导致了后面更严重的第二次爆发。

第二次爆发后,通过分析 Crash 统计平台上的173次 Crash 日志信息发现,Crash 代码地址都是“000000000007ce08”,如下图,由于静态库中的代码地址都是固定的,所以对底层 so 库进行了代码定位。

ae07428f195951073599066713833185.png

我分析底层 so 库代码习惯是使用 IDA,通过 IDA 定位到的问题代码如下图:

5c8663c090d90188948b20bede43f764.png

找到对应问题代码块,定位到直接原因是 fopen 文件返回空,fwrite 写入数据之前未做空判断。由于 fopen 是调用系统 API,系统 API 出现问题概率极小,所以一定是业务某些异常场景导致打开文件失败。

业务进行了哪种非法调用引发的异常场景?我们需要定位到问题场景的代码执行环境和具体的用户操作路径。此时只有完整复现这个问题,才能找到导致 fopen 失败的根因。

上面我们推测是周期性问题,与业务运营侧同学确认,没有周期性的活动发布,排除客观因素。

从 Crash 相关数据分析,除了定位到 libpush.so 的直接代码位置,没有太好的进展,所以根据对应上报高峰的时间段排查 top5 中的其它新增 Crash,发现其中一个 Crash 从上报时间、运行时间、机型几个纬度与直接 Crash 信息高度一致,大概率是同一个问题导致,查看对应的堆栈信息。

6ceea46965a99709e20ab72cc994ae87.png

综合定位到的底层 so 库的问题代码,分析原因是句柄超限后 fopen 打开失败导致为空,综合 App 长时间运行分析,句柄泄漏问题有14天(左右)的周期性共性条件,同时输出了占比top3问题机型的句柄上限都是1024。我们知道目前国内大多机型的句柄上限是10000+,不过由于我们自己的业务形态是基于定制设备的,定制设备更新换代较慢,机型较老,所以句柄上限较低,最终导致了问题主要集中在业务采购的定制系统设备,非定制的用户设备句柄虽然也会异常增加,但是在一个版本周期内是远远达不到句柄上限的,也就不会出现崩溃问题。

从以上信息推测崩溃问题是句柄泄漏导致超过系统上限,剩下的就是如何复现用户的操作路径和正向代码根因定位了。

问题复现

由于是句柄泄漏问题,输出打开的本地 fd 后,无法直接定位到具体 so。又因为是新版本新增问题,所以通过反向排除法,进行版本 diff,排查更新的代码。而业务代码未涉及句柄操作,故逐个进行依赖 SDK 还原,设备定时输出 fd 数量进行分析。

测试版本为线上有问题版本:

第一次测试记录:测试耗时:12h+,fd: 227 → 272

第二次测试记录:测试耗时:15h+,fd: 227 → 296

第三次测试记录:测试耗时:24h,fd: 227 → 313

e401608e5552f7fba79e2b6c1cef3e9b.png

句柄数量明显增加。

测试版本为还原更新的SDK版本:

第一次测试记录: 测试耗时:15h,fd: 193 → 198

第二次测试记录:测试耗时:21h,fd: 193 → 204,切换过账号一次

第三次测试记录:测试耗时:39h,fd: 193 → 210

句柄数量无明显增加。

对比 SDK 还原前后两个版本运行的数据得出结论,句柄异常增加的根因在线上版本所依赖的3个 SDK。这时候只需再依次对比3个依赖 SDK 的数据,定位到具体的问题 SDK 是时间的问题。同时我们也将排查定位进展同步给各自相关基础 so 库维护同学,发现之前其中一个 so 库的历史版本存在过句柄泄漏问题,和相应同学沟通了解相关信息。通过梳理底层 so 的句柄泄漏调用逻辑,增加调用日志,并对设备句柄数量持续观察,最终发现 so 侧存在一个逻辑:6小时轮询打开19个句柄,但是打开后没有正常关闭释放

此时问题的根因已大概定位,为了加快复现,我们把6小时轮询时间缩短为2分钟,运行一段时间后程序句柄数量达到1024上限发生崩溃,崩溃日志与线上崩溃日志完全相同,正向从用户角度复现了该问题。同时通过有无问题的两个版本 so 跑数据,对比后也证明问题的产生是由当前 so 库导致。至此,问题直接原因以及根本原因都已定位,问题修复后发版上线,线上验证通过。

为什么问题会集中爆发在 fwrite 方法的调用上?原因是业务其中一个场景需要30s轮询调用某个操作句柄的 API,高频调用 fwrite。它不能定位根本原因,不过暴露了直接原因,这也说明内存泄漏和句柄泄漏可能会报在任何代码位置。这里也让我们初期排查问题时,偏离了方向。不过这个就是这篇文章最想强调的内容,也是最想解决的问题,当出现这类问题,作为RD,我们要重点关注些什么?来快速纠正方向,快速定位问题。

下面我们简单介绍一下句柄泄漏是什么?如何处理?如何预防?Android中都有哪些常见的句柄?有助于我们后期快速排查定位句柄相关问题。

什么是句柄泄漏

句柄泄漏,就是当打开的资源未被正常释放,导致资源不能关闭回收。因为系统会为每个进程规定最大文件描述符上限数,一般 Linux 系统的进程最大句柄上限为1024,不过现在比较新的 Android 系统,上限升到32768,我们可以通过 adb shell ulimit -n 来查看:  

e64932a8fd72bff9e159fb0e107e0f64.png

截图为vivo findx2的设备文件描述符上限数

程序存在句柄泄漏问题时,对应的资源句柄不会被释放,当达到上限时,程序崩溃,报出异常信息。一般的异常信息有

  • Could not allocate dup blob fd

  • java.lang.RuntimeException: Could not read input channel file descriptors from parcel.

  • abort message 'could not create instance too many filesœ

  • java.io.IOException: Cannot run program "logcat": error=24, Too many open files

  • "Could not allocate JNI Env: %s", error_msg.c_str()

  • "Could not open input channel pair"

如果上报的日志信息包含这种,那大概就是句柄泄漏导致了。

注意:句柄泄漏和内存泄漏这类问题不属于业务逻辑问题,是进程分配的资源耗尽,导致再次分配时无足够的资源进行分配,所以当程序运行时,达到对应的资源上限后,就算普通的代码依然会直接报错,这个时候,上报的日志就是对应的代码位置,这点容易误导我们排查线上问题。

如何解决句柄泄漏

上面也提过,句柄泄漏和内存泄漏是一类问题,这类问题崩溃后,Crash 的位置可能不会明确的标出是哪里出现问题,最终的 Crash 日志也可能是普通代码。

问题用户操作路径存在共性的情况(复现难度较低)

  • 确定问题用户操作路径的共性

  • 根据用户操作路径,反复操作进行句柄数量监控并本地保存

  • 输出句柄异常增长情况,查看异常增长的句柄

  • 排查业务上,涉及句柄分配的代码

问题用户操作路径无共性的情况(复现难度较高)

  • 通过diff问题版本前后的代码,确定涉及句柄分配的代码改动

  • 通过排除法进行逐一回退对比,进行句柄数量监控和分析

  • 通过工具(so 库可以借助 IDA)进行问题代码定位,辅助分析问题

  • 确定问题代码或者问题依赖,再深入定位具体代码

以上的结论是建立在没有其它辅助手段基础上,实际排查过程中,我们还可以通过 Crash 平台上的辅助信息、梳理底层 so 库代码逻辑、积极与 so 侧同学沟通等角度进行辅助定位,也可以加速定位到问题代码。

定位句柄泄露相关命令和代码

1.查看设备句柄上限:adb shell ulimit -n

2.输出进程句柄:

private void listFd() {
    String tag = "FD_TAG";
    File fdFile = new File("/proc/" + android.os.Process.myPid() + "/fd");
    File[] files = fdFile.listFiles();
    int length = files.length;
    MerchantLogUtils.logFd(tag, "fd length: " + length);
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < length; i++) {
        File f = files[i];
        String strFile = Os.readlink(f.getAbsolutePath());
                sb.append(strFile + "\n");
    }
}

句柄泄漏常见问题以及分类

1.Andorid 常见的句柄泄漏问题

  • HandlerThread 的使用,要记得 release

  • IO 流操作打开后要在 finally 中 close

  • SQLite 数据库操作要把 cursor 实例 close

  • InputChannel相关,即 WindowManager.addView反复调用时,记得 removeView

  • Bitmap 进行 IPC 

这是常见的 Android 句柄泄漏的点,具体的原理分析资料很多。

2.部分 Android 的文件描述符的具体分类,可以缩小排查范围

c400686297c0e5bcf9f13f3db0bb8c07.png

如何预防和监控句柄泄漏

上面都是如何解决,其实更希望问题在线下或者灰度期间就暴露并且解决掉。

预防

  • 代码开发过程中,涉及句柄操作需“慎之又慎”,要经过充分的自测

  • 代码 CR 的 CheckList 增加“句柄相关代码的重点CR”,如上面介绍的 Android 常见的句柄泄露的场景

  • 版本需求改动涉及句柄创建时,QA 需重复多次操作对应路径,查看句柄情况

  • 测试粒度覆盖句柄,测试包定时输出句柄指标,达到对应的阈值后报警,端上同学介入排查句柄增长原因

  • 自动化测试中增加长时间运行时句柄数量的观测

观测

  • 线上定时获取设备句柄数量并且上报,建立句柄均值观测

  • 根据线上稳定期间的句柄数量均值,设置合理的报警阈值,及时感知到该类线上问题

总结

从 Crash 统计平台的相关数据可以看到,所有的 Crash 都在底层 so 库里,这类问题我们除了分析 Crash 统计平台提供的相关信息,是否还有其它可以辅助定位问题的手段呢?

  • 在全面定位过程中,也需要关注相同时间段的其他新增 Crash 信息,确认是否有相关性,如同为新增、机型、时间、地区、用户操作场景等等之间的相关性。

  • 通过对应的工具进行三方 so 的代码逻辑梳理,综合已有数据进行分析,如本次排查过程中使用 IDA 分析底层 so 库代码,定位到直接原因为 fwrite 之前未做空判断。由于系统 API 出现问题概率极小,如果之前处理过此类问题,综合 Crash 统计平台上的机型、运行时间等,从这就可以初步定位是句柄泄漏问题。

  • 积极同步进展以及所有可能的分析到底层 so 侧同学,我们定位到句柄泄漏,反向排除法定位问题过程中也同步到底层so同学,发现之前另一个底层so 解决过句柄泄漏问题,提前定位到问题 so,减少了问题定位的人力浪费。

  • 在与相关同学沟通时,要带有自己的分析和判断,有着重点的讨论,这样会极大地提升排查效率。

综上,当问题发生在平台底层库时,同时又无明显的用户操作路径,通过代码定位工具先尝试定位直接原因,再通过 Crash 平台上各方面的信息对比分析其相关性,配合测试数据,可以加快定位这类周期性问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1184113.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

家居品牌怎么做小红书投放,寻找家居达人的方式有哪些?

在当今社交媒体的时代&#xff0c;找到优秀的家居博主并不困难。但是&#xff0c;在找家居行业博主的时候&#xff0c;了解家居行业特性就很重要&#xff0c;今天来为大家分享一下家居品牌怎么做小红书投放&#xff0c;寻找家居达人的方式有哪些&#xff1f; 一、家居行业特性 …

共享盘的文件删除后能找回吗

在当今高度信息化的时代&#xff0c;数据和文件的重要性日益凸显。然而&#xff0c;由于各种原因&#xff0c;我们有时会不小心删除了一些重要的文件&#xff0c;这时候就会面临数据恢复的问题。那么&#xff0c;对于共享盘的文件&#xff0c;删除后是否还能找回呢&#xff1f;…

conda环境下version libcublasLt.so.11 not defined问题解决

1 问题描述 运行模型训练&#xff0c;错误信息如下&#xff1a; Traceback (most recent call last):File "/opt/Bert-VITS2/./text/chinese_bert.py", line 3, in <module>import torchFile "/root/anaconda3/envs/vits/lib/python3.9/site-packages/t…

如何从存档服务器上完全删除PDM用户

当创建新用户时使用“PDM 登录”类型&#xff08;如下图&#xff09;&#xff0c;PDM用户名和密码会存储于存档服务器的注册表中。 存档服务器的注册表位置如下&#xff1a; HKEY_LOCAL_MACHINE\SOFTWARE\SolidWorks\Applications\PDMWorks Enterprise\ArchiveServer\ConisioU…

【Vue】组件封装小技巧 — 利用$attrs和v-bind接收传递未定义的属性

使用介绍 在Vue.js中&#xff0c;$attrs 和v-bind可以用于组件的二次封装&#xff0c;以在封装的组件中传递父组件的属性和事件。这对于创建高度可定制的通用组件非常有用。 下面是一些示例代码&#xff1a; 假设你有一个名为MyButton的自定义按钮组件&#xff0c;它接受一些…

Apipost发起请求,能正确返回,日志却打印java.io.EOFException: null 的原因

http响应头首部Content-Length - 程序员大本营 http响应头首部Content-Length HTTP Content-Length深入实践-CSDN博客 用了这么久HTTP, 你是否了解Content-Length?-CSDN博客 具体分析可看上面参考文章。 解决办法&#xff1a;可在请求头加上Content-Length&#xff0c;准确…

linux服务器添置一块新硬盘操作

之前有一台ubuntu服务器&#xff0c;考虑未来存储容量可能不够&#xff0c;添加了一块新的硬盘&#xff0c;这是本次添置硬盘过程。 首次接上硬盘&#xff0c;提示&#xff1a; 没有找到新接入设备&#xff0c;查看接线&#xff0c;主板有个硬盘接线端子坏了&#xff0c;更换一…

机器视觉opencv答题卡识别系统 计算机竞赛

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 答题卡识别系统 - opencv python 图像识别 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满分5分…

新翔绩效考核系统

介绍&#xff1a; 新翔绩效考核系统&#xff0c;是新翔软件有限公司整合众多企事业单位绩效考核需求&#xff0c;而开发的网络版绩效管理软件。软件分前台手机端评分页面和后台电脑端管理页面&#xff0c;可实现360度绩效考核、KPI考核等模式考核&#xff0c;评分方式多样、设…

计算机毕设 基于情感分析的网络舆情热点分析系统

文章目录 0 前言1 课题背景2 数据处理3 文本情感分析3.1 情感分析-词库搭建3.2 文本情感分析实现3.3 建立情感倾向性分析模型 4 数据可视化工具4.1 django框架介绍4.2 ECharts 5 Django使用echarts进行可视化展示5.1 修改setting.py连接mysql数据库5.2 导入数据5.3 使用echarts…

【网络面试必问(6)】IP协议对网络包的转发

接上一篇&#xff1a;【网络面试必问&#xff08;5&#xff09;】网络收发数据及断开服务器&#xff08;四次挥手&#xff09; 在之前的博客中&#xff0c;我们提到过&#xff0c;网络传输的报文是有真实的数据包和一些头部组成的&#xff0c;目前我们了解的头部就有TCP头、IP头…

VueUse、View Transitions API实现暗黑模式主题动画切换效果

VueUse、View Transitions API实现暗黑模式主题动画切换效果 前言View Transitions API兼容版本 VueUse 正题效果安装代码 作者GitHub&#xff1a;https://github.com/gitboyzcf 有兴趣可关注&#xff01;&#xff01; 前言 View Transitions API View Transitions API 是原生…

华东理工大学漏洞报送证书

获取来源&#xff1a;edusrc&#xff08;教育漏洞报告平台&#xff09; url&#xff1a;主页 | 教育漏洞报告平台 兑换价格&#xff1a;20金币 获取条件&#xff1a;提交华东理工大学任意中危或以上级别漏洞 证书规格&#xff1a;附送图二实物及封皮

[ Linux Busybox ] nandwrite 命令解析

文章目录 相关结构体nandwrite 函数实现nandwrite 实现流程图 文件路径&#xff1a;busybox-1.20.2/miscutils/nandwrite.c 相关结构体 MTD 相关信息结构体 struct mtd_info_user {__u8 type; // MTD 设备类型__u32 flags; // MTD设备属性标志__u32…

【Proteus仿真】【STM32单片机】水质监测报警系统设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使用按键、LED、蜂鸣器、LCD1602、PCF8591 ADC、PH传感器、浑浊度传感器、DS18B20温度传感器、继电器模块等。 主要功能&#xff1a; 系统运行后&a…

汽车标定技术(七)--基于模型开发如何生成完整的A2L文件(2)

目录 1. 自定义ASAP2文件 2. asap2userlib.tlc需要修改的部分 3. 标定量观测量地址替换 3.1 由elf文件替换 3.2 由map文件替换 3.3 正则表达式&#xff08;含asap2post.m修改方法&#xff09; 4.小结 书接上文汽车标定技术(五)--基于模型开发如何生成完整的A2L文件(1)-C…

考研408-计算机网络 第二章-物理层学习笔记及习题

第二章 物理层 一 通信基础 1.1 物理层基本概念 1.1.1 认识物理层 物理层目的&#xff1a;解决如何在连接各种计算机的传输媒体上传输数据比特流&#xff0c;而不是具体的传输媒体。 物理层主要任务&#xff1a;确认与传输媒体接口有关的一些特性&#xff0c;需要进行定义标…

Kafka(消息队列)--简介

1、kafka&#xff1a; 是一个高吞吐的分布式消息系统&#xff0c;与Hdfs比较相似&#xff0c;但是与hdfs的区别是在于hdfs是存储的是历史的、海量的数据&#xff0c;然而kafka存储的是实时的、最新的数据。 2、消息队列&#xff1a; 指的是在Kafka中的数据队列。可以存放数据在…

华为数通方向HCIP-DataCom H12-831题库(多选题:141-160)

第141题 项目实施方案主要包含哪些项? A、风险控制措施 B、人员安排 C、时间规划 D、割接流程 答案:ABCD 解析: 项目实施方案主要包括:时间规划、人员安排、割接流程、风险控制措施。 第142题 以下哪些广域网链路属于报文交换网? A、PSIN B、ISDN C、X.25 D、ATM E、MSTP…

大语言模型(LLM)综述(六):大型语言模型的基准和评估

A Survey of Large Language Models 前言7 CAPACITY AND EVALUATION7.1 基本能力7.1.1 语言生成7.1.2 知识利用7.1.3 复杂推理 7.2 高级能力7.2.1 人类对齐7.2.2 与外部环境的交互7.2.3 工具操作 7.3 基准和评估方法7.3.1 综合评价基准7.3.2 评估方法 7.4 实证评估7.4.1 实验设…