0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

一只發(fā)生概率小于萬分之一的Bug

程序人生 ? 來源:程序新視界 ? 作者:二師兄 ? 2022-05-05 09:36 ? 次閱讀

在開始這篇文章之前想先說一句:如果一套系統(tǒng)暫時沒問題,那只是因為它的并發(fā)量不夠而已。

上周在查看系統(tǒng)日志時,發(fā)現(xiàn)了一條與眾不同的日志。日志中有一半內(nèi)容是正常的報文數(shù)據(jù),而另一半內(nèi)容是0x00這樣的空數(shù)據(jù)。

雖然系統(tǒng)沒拋出任何異常,但這些日志肯定是反常的。多年的經(jīng)驗告訴我,這其中一定有什么不對的地方,加上好奇心的驅(qū)使,終于揭開了一個隱藏非常深的Bug。

有時候找到Bug,解決Bug很容易,難的是如何發(fā)現(xiàn)Bug,并推理出哪里出問題解決。下面就帶大家來剖析一下這個Bug。

奇怪的日志輸出

一個調(diào)用外部接口的基礎(chǔ)類,打印出類似如下的日志:

abcdabcdabcdabcdabcdabcdabcd《0x00》《0x00》《0x00》《0x00》《0x00》

其中前面的abcd是正常的業(yè)務(wù)數(shù)據(jù),后面莫名其妙的多出了很多《0x00》。

那么,這個基礎(chǔ)工具類有多基礎(chǔ)?多處使用該方法,每天大約被調(diào)用幾十萬次吧,而上面的情況一天只會出現(xiàn)幾次。就是那么巧,恰好被看到了。

查看代碼,初步推斷,可能是byte數(shù)組轉(zhuǎn)String時,byte數(shù)組后半部分為空或存在一些無法轉(zhuǎn)換的數(shù)據(jù)導(dǎo)致的。

舊代碼分析

這里先把業(yè)務(wù)代碼脫敏,寫成一個demo展示給大家看看:

public static void oldCode() throws IOException

{

// 通過HttpURLConnection讀取的外部系統(tǒng)返回的流

InputStream in = new ByteArrayInputStream(“abc”.getBytes());

// 明確知道的報文長度(解析Header獲得)

int bodyLen = 2048;

byte[] body = new byte[bodyLen];

int recvLen = 0; while (recvLen 《 bodyLen)

{

recvLen = in.read(body, recvLen, bodyLen - recvLen);

if(recvLen == -1){

break;

}

}

System.out.println(new String(body, “GBK”));}

上述代碼進行了業(yè)務(wù)脫敏處理,僅為還原基本的使用過程。

業(yè)務(wù)場景的大概使用流程是:第一,通過HTTP調(diào)用遠程接口;第二,讀取接口返回的字節(jié)流,Inputstream;第三,解析字節(jié)流,存入字節(jié)數(shù)組;第四,將字節(jié)數(shù)組轉(zhuǎn)換為String。

而日志中看到的異常內(nèi)容,便是打印String時出現(xiàn)的。前面我們已經(jīng)推斷,出現(xiàn)《0x00》的可能性是字節(jié)數(shù)組有一部分為空導(dǎo)致或數(shù)據(jù)錯誤導(dǎo)致的。

上述代碼有一個明顯的錯誤,你是否能夠看出來?根據(jù)代碼原始的寫法,推測之所以出現(xiàn)這個錯誤是因為使用者對InputStream的read方法并不熟悉導(dǎo)致的。

這里讀者先自行閱讀看看上述代碼的Bug在哪里,下面我們來介紹一下InputStream的read方法。

InputStream的read方法

InputStream這個抽象類是表示字節(jié)輸入流的所有類的超類,它提供了3個經(jīng)常被使用的read()方法:

read(),無參方法。該方法從輸入流中讀取數(shù)據(jù)的下一個字節(jié)。返回0到255范圍內(nèi)的int字節(jié)值。如果因為已經(jīng)到達流末尾而沒有可用的字節(jié),則返回值 -1 。該方法會處于阻塞狀態(tài),等待數(shù)據(jù)的到達,直到返回值為-1或拋出異常。

read(byte b[], int off, int len):將輸入流中最多l(xiāng)en個數(shù)據(jù)字節(jié)讀入byte數(shù)組。嘗試讀取len個字節(jié),但讀取的字節(jié)也可能小于該值。以整數(shù)形式返回實際讀取的字節(jié)數(shù)。

read (byte[] b):從輸入流中讀取一定數(shù)量的字節(jié),并將其存儲在緩沖區(qū)數(shù)組b中。以整數(shù)形式返回實際讀取的字節(jié)數(shù)。

分析一下上面的三個方法。

其中第一個方法,本質(zhì)上來說后兩個方法都是調(diào)用第一個方法來實現(xiàn)的,但第一個方法直接使用缺點很明顯,就是處理效率低下,一個字節(jié)一個字節(jié)的讀。而后兩個方法都加入了byte數(shù)組,用來作為緩存區(qū)。

而第三個方法又相當于第二個方法被如下方式調(diào)用:

read(b, 0, b.length)

而有Bug的代碼中使用的是第二個方法。

Bug分析

看了read方法的API說明,你是不是已經(jīng)找到Bug了?對的,當初寫這段代碼的人把read方法返回值理解錯了。

recvLen = in.read(body, recvLen, bodyLen - recvLen);

最初寫代碼的人可能把read方法的返回值當中參數(shù)off經(jīng)過讀取之后新的位置了。這樣在調(diào)用read方法之后,獲得了填充的位置,然后拿總長度減去已經(jīng)填充的位置,再繼續(xù)讀取后面的內(nèi)容,繼續(xù)填充。

但實際上read方法的返回結(jié)果是:以整數(shù)形式返回實際讀取的字節(jié)數(shù),可能與off的位置值相同,但并不是off的位置。

下面來分析一下while循環(huán)中的邏輯處理情況:

while (recvLen 《 bodyLen)

{

recvLen = in.read(body, recvLen,

bodyLen - recvLen);

if(recvLen == -1){

break;

}}

我們舉個例子來推演一下2種情況(為了方便推算,暫且用比較小的數(shù)來舉例)。

情況一:假設(shè)bodyLen長度為10,read一次性讀完。

在這種情況中,先進入while循環(huán),read一次性讀完,返回值為10,此時recvLen賦值為10,不再滿足循環(huán)條件(recvLen 《 bodyLen),退出循環(huán),繼續(xù)執(zhí)行。此時,代碼沒問題。這種情況可能占到99.9%-99.99%(取決于請求頻次和報文大小)。

情況二:假設(shè)bodyLen長度為10,read 2次讀完(發(fā)生粘包拆包現(xiàn)象)。

第一次循環(huán),read讀取6個字節(jié)長度,返回值為6,recvLen賦值為6。第二次循環(huán),off參數(shù)取recvLen的值為6,讀取剩余4個字節(jié)(10 - 6)。完成第二次讀取,循環(huán)本應(yīng)該結(jié)束的,但你會發(fā)現(xiàn)此時recvLen被賦值為4,依舊滿足while循環(huán)的判斷條件(recvLen 《 bodyLen),進行下一輪讀取。

下一輪讀取時,off變?yōu)?,len變?yōu)椋?0 - 4)。本來經(jīng)過第二輪循環(huán)off已經(jīng)讀取到10了,現(xiàn)在又指定為4,又去流中讀取。這就造成了日志中出現(xiàn)很多《0x00》。

Bug原因

經(jīng)過上述分析,我們已經(jīng)找到Bug,并獲得了Bug原因。

首先,Bug之所以沒有大面積爆發(fā),那是因為大多數(shù)請求都是一次性讀完流中的數(shù)據(jù),循環(huán)直接結(jié)束,當不會進入第二次循環(huán)時,這個Bug就被隱藏了。

其次,Bug之所以發(fā)生除了使用者對API的返回值不了解,更重要的原因是對于read方法可能會將結(jié)果分多次返回(粘包拆包現(xiàn)象)不了解。

Bug改造

找到原因,改造起來就非常容易了。針對demo我們重新改造一下:

public static void oldCode()

throws IOException

{

// 通過HttpURLConnection讀取的外部系統(tǒng)返回的流

InputStream in = new ByteArrayInputStream(“abc”.getBytes());

// 明確知道的報文長度(解析Header獲得)

int bodyLen = “abc”.getBytes().length;

System.out.println(bodyLen);

byte[] body = new byte[6];

int recvLen = 0; while (recvLen 《 bodyLen)

{

// 改造點1

int currentLen = in.read(body, recvLen, bodyLen - recvLen);

if(currentLen == -1){

break;

}

// 改造點2

recvLen += currentLen;

}

System.out.println(new String(body, “GBK”));}

上述改造只改動了兩處,將read方法的返回值用新變量接收,然后讓recvLen每次累加read讀取的字節(jié)數(shù)。

改造是不是非常簡單?正應(yīng)了那句話:改bug很容易,難的是如何找到bug。

小結(jié)

有時候我們對自己寫的代碼很自信,有時候總以為代碼之前能夠正常運行,以后也能夠正常運行。但往往事與愿違,誰能想到一直“運行良好”的代碼中深藏著這樣的Bug?所以,還是那句話,如果你覺得你的代碼沒問題,那只是因為系統(tǒng)的并發(fā)量還不夠而已。代碼不僅要實現(xiàn)功能,還要滿足性能和健壯性。

審核編輯 :李倩

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 數(shù)組
    +關(guān)注

    關(guān)注

    1

    文章

    412

    瀏覽量

    25869
  • BUG
    BUG
    +關(guān)注

    關(guān)注

    0

    文章

    155

    瀏覽量

    15635

原文標題:捕獲了一只發(fā)生概率小于萬分之一的Bug

文章出處:【微信號:coder_life,微信公眾號:程序人生】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    傳諾基亞大裁員,大中華區(qū)占五分之一

    諾基亞(Nokia Corporation)正在積極推進其成本削減計劃,以應(yīng)對當前的市場挑戰(zhàn)。據(jù)最新消息,諾基亞已在大中華區(qū)裁減了近2000名員工,占該地區(qū)員工總數(shù)的五分之一,這數(shù)字令人震驚。
    的頭像 發(fā)表于 10-18 17:16 ?719次閱讀

    金天弘科技“萬分之一級”高精度MEMS諧振式壓力芯片和傳感器全面實現(xiàn)國產(chǎn)自主可控

    金天弘科技(北京)有限公司完成了萬分級高精度MEMS諧振式壓力芯片和傳感器的研制,為新代軍事裝備和先進工業(yè)領(lǐng)域?qū)崿F(xiàn)全國產(chǎn)自主可控和升級換代提供核心關(guān)鍵芯片產(chǎn)品。
    的頭像 發(fā)表于 10-10 15:26 ?227次閱讀
    金天弘科技“<b class='flag-5'>萬分之一</b>級”高精度MEMS諧振式壓力芯片和傳感器全面實現(xiàn)國產(chǎn)自主可控

    LM2902D的放大精度為多少?

    LM2902D的放大精度為多少?我想采用高精度電阻(精度:萬分之五)配置LM2902D的放大倍數(shù),放大倍數(shù)誤差范圍是多少? 有沒有其他精密運放推薦?電源有+12V、-12V、+5V、-5V供電,謝謝!
    發(fā)表于 09-11 06:27

    中國人工智能大模型占全球的三分之一

    大模型領(lǐng)域的全球占比已突破三分之一大關(guān),具體數(shù)值高達36%,這成就不僅彰顯了我國在該領(lǐng)域的深厚積累與快速發(fā)展,也標志著中國在全球AI競賽中緊隨美國之后,穩(wěn)居第二的堅實地位。
    的頭像 發(fā)表于 07-08 15:21 ?407次閱讀

    中國大陸晶圓制造產(chǎn)能飆升,預(yù)計2025年占全球三分之一

    制造產(chǎn)能將在未來幾年內(nèi)實現(xiàn)顯著增長,預(yù)計到2025年,其月產(chǎn)能將達到驚人的1010片,占據(jù)全球晶圓制造總產(chǎn)能的近三分之一
    的頭像 發(fā)表于 06-26 11:49 ?927次閱讀

    2030年RISC-V將占全球市場四分之一

    據(jù)Omdia的最新研究,預(yù)計到2030年,RISC-V處理器將占據(jù)全球近四分之一的市場份額。盡管工業(yè)領(lǐng)域仍將是該技術(shù)最大的應(yīng)用領(lǐng)域,但預(yù)計開放標準指令集架構(gòu)(ISA)將在汽車領(lǐng)域?qū)崿F(xiàn)最強勁的增長
    的頭像 發(fā)表于 05-23 08:36 ?321次閱讀
    2030年RISC-V將占全球市場四<b class='flag-5'>分之一</b>

    臺積電前4月營收增26.2%,預(yù)計二季度營收再增三分之一

    自3月份以來,臺積電收入增長加快至34.3%,預(yù)計第二季度營收將再增長約三分之一,這主要得益于人工智能半導(dǎo)體的旺盛需求。全球智能手機行業(yè)在今年前三個月實現(xiàn)恢復(fù)性增長
    的頭像 發(fā)表于 05-10 16:18 ?332次閱讀

    求助,關(guān)于STM8S003F3串口問題求解

    的數(shù)據(jù)幀,也就是丟包。丟包率大概在1%左右,不知道有沒有朋友遇到過這種情況并有解決的辦法。由于設(shè)備比較特殊,預(yù)期丟包率不應(yīng)該高于萬分之一,通信模式為自定義協(xié)議 485 8bit 9600 1stopBit parity-none
    發(fā)表于 05-10 06:38

    怎么用電容電感來代替四分之一波長微帶線呢?

    前陣子,有號友問過我關(guān)于怎么用電容電感來代替四分之一波長微帶線的問題。微波工程上有個現(xiàn)成的結(jié)論,所以就推薦過去了,沒有去仔細推導(dǎo)那個結(jié)論是怎么來的。
    的頭像 發(fā)表于 05-06 11:45 ?1639次閱讀
    怎么用電容電感來代替四<b class='flag-5'>分之一</b>波長微帶線呢?

    預(yù)測:2024年全球電動汽車銷量將占總銷量五分之一以上

    預(yù)計中國市場仍將保持領(lǐng)先地位,至2024年電動車銷量將達約1000輛,占中國汽車總銷量的45%。美國市場方面,預(yù)計今年電動汽車銷量將占新車銷售的九分之一。
    的頭像 發(fā)表于 04-23 16:22 ?739次閱讀

    Mozilla重啟Firefox原生標簽頁組研發(fā)?

    查閱IT之家2015年報導(dǎo)顯示,早在2009年,F(xiàn)irefox即開始提供款名為Panorama的標簽頁組功能。然而,根據(jù)Mozilla的數(shù)據(jù)分析,這功能的實際使用率僅為萬分之一,故而在2016年被去除,Mozilla當時推薦
    的頭像 發(fā)表于 03-19 14:16 ?291次閱讀

    電力模塊電源常見的幾個小問題深度剖析

    高壓尖脈沖(highvoltagespikes):指峰值達6000v,持續(xù)時間從萬分之一秒至二分之一周期(10ms)的電壓。這主要由于雷擊、電弧放電、靜態(tài)放電或大型電氣設(shè)備的開關(guān)操作而產(chǎn)生。
    發(fā)表于 03-13 11:27 ?239次閱讀

    低溫下AD2S83內(nèi)部VCO頻率突變正常值的3倍,導(dǎo)致輸出角速度變成正常值的三分之一怎么解決?

    ,角度輸出正常,角速度輸出變成正常值的三分之一。在排查過程中,斷開R6前端的角速度反饋,在R6前端輸入個直流8V的角速度模擬信號,常溫下測得AD2S83內(nèi)部VCO輸出鋸齒波如上右圖所示,頻率與計算
    發(fā)表于 12-07 07:26

    AD5422無法滿足千分之一的精度要求怎么解決?

    我研發(fā)的項目用到AD5422芯片做模擬量輸出4-20mA,碰到如下問題: 通過配置控制寄存器,讓AD5422工作在4-20mA輸出模式,外置的Rset選用的千分之一的15K電阻。但是實測的時候發(fā)現(xiàn)
    發(fā)表于 11-23 06:35

    迄今世界最靈敏力傳感器問世,可測量電子重量的十分之一

    ? 據(jù)英國《新科學(xué)家》網(wǎng)站11月2日報道,法國科學(xué)家利用極冷的銣原子,制造出了迄今最靈敏的力傳感器,其可測量拎起單個電子所需力十分之一大小的力,未來有望揭示全新力的存在。相關(guān)論文已經(jīng)提交預(yù)印本
    的頭像 發(fā)表于 11-07 08:45 ?325次閱讀