写点什么

如何调试一个无法重现的错误?

  • 2018-12-12
  • 本文字数:3122 字

    阅读完需:约 10 分钟

如何调试一个无法重现的错误?

2018 年 10 月 10 日,我们的团队发布了一个新版本的 React Native 应用程序。我们很高兴又为我们的用户交付了新功能。


但是,恐怖的事情发生了!


发布几个小时后,我们突然收到很多 Android 崩溃事件。



Android 版本上发生了 10000 次崩溃


我们的崩溃报告工具Sentry像着火了一样!


所有的新错误都是类似“JSApplicationIllegalArgumentException Error while updating property ‘left’ in shadow node of type: RCTView”这样的。


在 React Native 中,如果你使用错误的类型设置属性,通常会发生这种情况。但是,为什么我们在测试应用程序时没有发现这个错误?我们的新版本已经在多个设备上测试过了。


此外,错误似乎是随机的,似乎在遇到属性和阴影节点类型的组合时会发生这个错误。以下是其中的 3 个错误:



根据 Sentry 的报告,这些错误似乎在任意设备和任意 Android 版本上都会发生。


重现错误

修复错误的第一步是重现错误。所幸的是,因为有 Sentry 日志,我们知道用户在触发崩溃之前正在做什么。



绝大多数的崩溃都是发生在用户打开应用程序的时候。


现在我们也尝试重现一下。我们在 6 台不同的 Android 设备上安装从应用商店下载的 App,可惜的是,并没有发生崩溃!而且,在开发模式下就更不可能在本地重现这个错误了。


看来这样做似乎毫无意义。无论如何,崩溃似乎是随机发生的。发生崩溃的概率约为 10%,也就是说,基本上启动 App10 次会有一次发生崩溃。

分析堆栈跟踪信息

为了能够重现崩溃,我们试着去了解问题出在哪里。


如前所述,我们遇到了几个不一样的错误。它们都有类似但不完全相同的堆栈跟踪信息。


我们先来分析第一个:


java.lang.ArrayIndexOutOfBoundsException: length=10; index=-1    at android.support.v4.util.Pools$SimplePool.release(Pools.java:116)    at com.facebook.react.bridge.DynamicFromMap.recycle(DynamicFromMap.java:40)    at com.facebook.react.uimanager.LayoutShadowNode.setHeight(LayoutShadowNode.java:168)    at java.lang.reflect.Method.invoke(Method.java)    ...
java.lang.reflect.InvocationTargetException: null at java.lang.reflect.Method.invoke(Method.java) ...
com.facebook.react.bridge.JSApplicationIllegalArgumentException: Error while updating property 'height' in shadow node of type: RNSVGSvgView at com.facebook.react.uimanager.ViewManagersPropertyCache$PropSetter.updateShadowNodeProp(ViewManagersPropertyCache.java:113)
复制代码


我们找到了发生错误的地方:android/support/v4/util/Pools.java。


我们已经非常深入到 Android 支持库,但不确定现在可以从中推断出多少信息。

使用另一种方式

另一种方法是检查我们在新版本代码中所做的修改,特别是那些会影响原生 Android 代码的修改。我们发现了 2 个可能性:


  • 我们升级了 Native Navigation,这是一种在 Android 上为每个屏幕使用原生片段的导航解决方案;

  • 我们升级了 react-native-svg。有一些与 SVG 组件相关的异常,但有些与它没有关系,所以很难说。


因为无法重现错误,我们最好的选择是:


  • 回退 2 个库中的一个;

  • 只发布给 10%的用户;

  • 与这些用户确认,看看新版本有没有发生崩溃。这样就可以验证我们的假设。



要回退哪个库呢?


一种办法是通过抛硬币来决定,但我们真的要这么做吗?

再深入一些

好吧,让我们深入挖掘之前的堆栈跟踪信息,看看是否可以确定选择回退哪个库。


public static class SimplePool implements Pool {    private final Object[] mPool;    private int mPoolSize;    ...    @Override    public boolean release(T instance) {        if (isInPool(instance)) {            throw new IllegalStateException("Already in the pool!");        }        if (mPoolSize < mPool.length) {            mPool[mPoolSize] = instance;            mPoolSize++;            return true;        }        return false;    }
复制代码


以上是崩溃发生的地方。错误是java.lang.ArrayIndexOutOfBoundsException: length=10; index=-1,意思是说,mPool 是一个大小为 10 的数组,但 mPoolSize = -1。


除了上面的 recycle 方法之外,可以修改 mPoolSize 的另一个地方是 SimplePool 类的 acquire 方法:


public T acquire() {    if (mPoolSize > 0) {        final int lastPooledIndex = mPoolSize - 1;        T instance = (T) mPool[lastPooledIndex];        mPool[lastPooledIndex] = null;        mPoolSize--;        return instance;    }    return null;}

复制代码


因此,导致 mPoolSize 变为-1 的唯一可能是在 mPoolSize=0 时继续执行 mPoolSize–。 但在 mPoolSize > 0 时,这种情况怎么可能会发生呢?


我们在 Android Studio 中设置了一个断点,并检查启动应用程序时发生了什么。我的意思是,因为有一个 if 条件,这段代码不应该会出现故障!


出乎意料!


DynamicFromMap 持有对 SimplePool 的静态引用。


在精心设置断点并点了几十次 Play 按钮后,我们发现,mqt_native_modules 线程调用了 SimplePool.acquire 和 SimplePool.release(React Native 用来管理 React 组件的样式属性,如下图显示的组件 width 属性)。



但同时也被主线程调用!



从上面我们可以看到,它被用于更新主线程上的 fill prop,这个属性通常属于 react-native-svg 组件!实际上,react-native-svg 只在版本 7 之后才开始使用 DynamicFromMap 来提高原生 svg 动画的性能。


函数实际上被 2 个线程调用,但 DynamicFromMap 没有以线程安全的方式使用 SimplePool。“线程安全”又是什么鬼?

线程安全理论

因为 JavaScript 是单线程的,因此 JavaScript 开发人员通常不需要处理线程安全问题。


另一方面,Java 支持并发或多线程概念。多个线程可以在单个程序中运行,并且可能会并发访问公共数据结构,可能会导致意外的结果。


让我们举一个简单的例子,在下图中,线程 A 和线程 B 都:


  • 将整数读入内存;

  • 增加它的价值;

  • 将它返回。



在线程 A 完成更新之前,线程 B 可能会访问数据的值。我们期望它们是两个单独的递增值操作,最终结果为 19,但结果可能会是 18。对于这样情况,数据的最终状态取决于线程操作的顺序,称为竞态条件。竞态条件的问题在于它们不一定总是会发生。对于上述的情况,线程 B 在递增值之前还有更多的工作要做,为线程 A 提供足够的时间来更新值。这就解释了重现崩溃的随机性和不可能性。


如果操作可以由很多线程同时完成,则数据结构被认为是线程安全的,就不会有出现竞态条件的风险。


当一个线程读取一个特定数据元素时,不应该让其他线程修改或删除这个元素(这称为原子性)。在我们之前的示例中,如果更新周期是原子的,就可以避免出现竞态条件。线程 B 将等待线程 A 完成操作。



由于 DynamicFromMap 持有对 SimplePool 的静态引用,因此不同线程的多个 DynamicFromMap 调用导致可以同时调用 SimplePool 的 acquire 方法。


在上图中,线程 A 调用 acquire 方法,得出条件为 true,但尚未减小 mPoolSize 的值(与线程 B 共享),而线程 B 同时调用该方法,并得出相同的条件。然后每个单独的调用都将减少 mPoolSize 的值,这就是为什么你会获得一个错误的值。

修复错误

我们在 react-native 上发现了一个未合并的 PR,这个 PR 修复了线程安全问题。



然后,我们部署了一个修补版本的 react native,将其发布给我们的用户。崩溃问题终于得到了解决!



这个修复将包含在 React Native 的下一个小版本 0.57 中。


为了修复这个错误,我们确实做出了很大的努力,但这也是一个深入了解 react-native 和 react-native-svg 的绝佳机会。


英文原文:


https://blog.bam.tech/developper-news/debugging-a-non-reproducible-crash


2018-12-12 14:522111
用户头像

发布了 731 篇内容, 共 474.9 次阅读, 收获喜欢 2008 次。

关注

评论 1 条评论

发布
暂无评论
发现更多内容

数据为王!深度挖掘天猫商品详情接口,赋能电商运营新策略

tbapi

天猫 天猫商品详情数据接口 天猫API接口 天猫商品数据采集

客户在哪儿AI助力ToB销售精准混圈子

客户在哪儿AI

ToB营销 ToB获客 ToB增长 ToB销售 大客户销售

Advanced RAG 10:引入检索评估、知识精练的 CRAG 技术详解

Baihai IDP

AI 白海科技 LLMs 企业号 7 月 PK 榜 rag

基于51单片机设计的红外遥控器

DS小龙哥

7月月更

24年开封有资质等保机构叫什么名字?电话多少?

行云管家

等保 等保测评 开封

AI 应用实战营 - 作业 四 - 文生图

德拉古蒂洛维奇

Sentieon快速入门指南

INSVAST

软件 基因数据分析 生信服务

《第一章、HarmonyOS介绍》01-HarmonyOS简介

清风论

华为 前端 HarmonyOS 鸿蒙开发

为何我们决定从零开始创建 NGINX Gateway Fabric

NGINX开源社区

开源 开源软件 NGINX Ingress Controller API 开发 Kubernets

教你基于MindSpore用DCGAN生成漫画头像

华为云开发者联盟

人工智能 模型训练 华为云 华为云开发者联盟 企业号2024年7月PK榜

企业的分层运维对象监控指标体系建设

嘉为蓝鲸

可观测 指标管理 指标建设

从CVE-2024-6387 OpenSSH Server 漏洞谈谈企业安全运营与应急响应

京东科技开发者

软件测试学习笔记丨Allure2报告中添加附件-html

测试人

软件测试 测试开发

开通GPT4.0、GPT-4o的方法,门槛超低,三分钟学会

蓉蓉

GPT-4 gpt4o

中国标网正式公布BizDevSecOps能力成熟度模型标准,嘉为科技参编

嘉为蓝鲸

DevOps BizDevOps

客户在哪儿AI告诉你,与什么样的平台合作才是ToB企业的最优选

客户在哪儿AI

ToB营销 ToB获客 ToB增长 ToB企业

重磅来袭!MoneyPrinterPlus一键发布短视频到视频号,抖音,快手,小红书上线了

程序那些事

工具 程序那些事 AIGC

客户在哪儿AI:新媒体时代ToB企业做传播该如何选择媒体

客户在哪儿AI

内容营销 ToB营销 ToB获客 ToB增长

文献解读-多组学-第十七期|《基于多组学分析和综合模型的三阴性乳腺癌腋窝淋巴结转移预测》

INSVAST

基因数据分析 生信服务 多组学

透视开源生态,OSGraph——GitHub全域数据图谱的智能洞察工具

汀丶人工智能

人工智能

精简库存,避免售罄 零售商常见错误及策略

第七在线

2024 「全球软件研发技术大会】-刘兴东分享京东的AIGC革新之旅

京东科技开发者

人工智能赋能教育:华为云推动宝安中学迈进教育+AI新时代

最新动态

公开课 | 利用AI智能体实现自动化公开课

测试人

软件测试

证券行业采购堡垒机的六大必要性看这里!

行云管家

网络安全 金融 证券 数据安全 堡垒机

嘉为蓝鲸WeOps智能化模块:专属于运维的智能助手正式面世!

嘉为蓝鲸

运维 大模型 weops

ITSM流程落地经验之请求管理

嘉为蓝鲸

ITSM 流程管理 请求管理

【论文速读】| 用于安全漏洞防范的人工智能技术

云起无垠

利用AI智能体实现自动化公开课

霍格沃兹测试开发学社

淘宝商品详情api接口:快速获取商品主图,价格,

技术冰糖葫芦

API 文档 API 开发 API 协议 pinduoduo API

如何调试一个无法重现的错误?_语言 & 开发_Alexandre Moureaux_InfoQ精选文章