近來異常充實,專案火燒屁股,大小茶包報案照常受理,生活好不精彩。遇到一枚奇妙茶包,追了好一會兒,謎底卻令人莞爾,為枯躁生活平添一絲趣味,特記上一筆。

同事報案,表單系統在歸檔時有個錯誤重試機制,出錯時自動休眠 30 分鐘再試,另外,系統亦接受程式指定於特定時間(稱為喚醒時間)重試。

監看報表出現多筆詭異喚醒時間,排定在 2058、2075、2052、2081、2068、2057、2055... 等 40-70 年後的日期,預訂於遙不可及的未來執行。嘗試分析詭異喚醒時間與初次執行的關聯,找不出任何規律。

初步檢查歸檔程式,確實有一段邏輯在系統出錯時傳回 DateTime.Now.AddMinutes(120),明確指定兩小時後重試,喚醒時間沒理由亂跳。同事甚至一度懷疑是資料庫錯亂導致,但僅有單一欄位資料不對,其餘資料正常的機會微乎其微(機率應不會比 GUID 碰撞高)。

從 Log 檔找到歸檔程式傳回 2058-06-27 20:00:54 的鐵據(俗話說:Log 寫得好,除錯沒煩惱 :P ),直接排除資料庫涉案的嫌疑,辦案重心回到歸檔程式本身,仔細翻找,看到一段程式碼,忍不住笑了出來:

    if (theDate != DateTime.Today)
    {
        Random rnd = new Random(Guid.NewGuid().GetHashCode());
        //當日非營業日,休眠至次日並稍作延遲再嘗試重新歸檔
        wrc.Sleep(theDate.AddSeconds(rnd.Next()), 
            "非營業日,將於次日嘗試重新歸檔:" + 
            theDate.ToString("yyyy/MM/dd"));
        Response.End();
    }

程式原意是遇非營業日延後一天再試並加上亂數延遲,避免大量排程擠在同一時間執行,但此處犯了兩個錯誤:第一是漏了 AddDays(1) 未將時間延後一天,第二是製造延遲誤用 Random.Next(),誤以為 Random.Next() 會比照 VBScript Rnd() 或 Math.random() 傳回介於 0 與 1 間的小數,但 .NET Random.Next() 傳回的是 0 – int.MaxValue(約20億) 間的隨機正整數,變成延遲 0 - 63 年不等,就是 2058、2081 等神奇未來年份加無規律的由來。程式寫好多年,因非營業日執行的機率極低,這支 Bug 才得以隱身至今。

回到程式碼,可改寫如下:

    if (theDate != DateTime.Today)
    {
        //建構式不需傳入種子,直接以當下時間隨機決定
        Random rnd = new Random();
        //當日非營業日,休眠至次日並稍作延遲再嘗試重新歸檔
        //使用 Random.Next(N) 產生大於等於0但小於N的隨機整數
        //另外也可用 Random.Next(M, N) 產生大於等於M但小於N的隨機整數
        wrc.Sleep(theDate.AddDays(1).AddSeconds(rnd.Next(3600)), 
            "非營業日,將於次日嘗試重新歸檔:" + 
            theDate.ToString("yyyy/MM/dd"));
        Response.End();
    }

就醬,結束一次被茶包逗樂的難得經驗。


Comments

Be the first to post a comment

Post a comment