推 KekeMonster: 是我木眼嗎,result 本來就該是 true,false 才是靈 06/21 18:18
→ KekeMonster: 異現象吧? 06/21 18:18
__lCurrentTime 會漸漸變大,而它比起另一個變數,只大6
因此這個 forloop 執行六次左右就該結束
結束時 __lCurrentTime<=__lTimeout 這個判斷條件應為 false
而 __result 應該不會被更新到
目前的問題就是不結束,__result 永遠為 true
我應該沒寫錯
→ qrtt1: 不要把找不出 root cause 當靈異現象啊 @o@ 06/21 19:08
我常在板上挨罵就算了,他們可資深得不得了
工研院出來的,Trace 過整個 Unix,
我會懷疑他們在 java 上的功力,畢竟碰沒多久
但不會懷疑他們在 c/c++, multi-thread 的功力 (因為也曾是 multithread 講師)
因此比如變數被另一個 thread 干擾,這個也排除了
我自己前陣子是解掉一個,原因就是 multi-thread
變數 a 的值,我一直 trace 都是我要的,突然一轉眼就變了
但是同事避這個問題的方法很簡單:他沒必要共享變數,他的是 local 變數
既然變數沒被另一個 thread 參考到,那值就沒有變的理由
那為什麼會出現像
boolean a = (5 <= 3);
結果 a 為 true, 這樣很根本上的錯誤呢?
-------------------
問題可能解掉了
方法是把一個四千多行的函式縮小,拆成數個
因此目前我們只能認為,這也是 java 的極限
(我不是說不能寫四千多行喔,別自己去寫一個四千多行的來打我臉,不是這樣喔)
麻煩的就是,既然炸了也好歹打聲招呼,丟個 fatal 出來
都沒有啊,默默的...
那我們就有一堆不確定的猜測,大量的測試
操死一堆同事...
※ 編輯: HuangJC (60.251.197.55), 06/21/2016 19:26:31
推 KekeMonster: result 已經在 loop 裡被更新 6 次 true 了怎麽會是 06/22 00:21
→ KekeMonster: false 06/22 00:21
→ dennisxkimo: 我怎麼看都不覺得結果是flase 06/22 11:44
→ HuangJC: 不會是false,但也不該被執行到 06/22 11:58
→ HuangJC: 我們是因為奇怪它為什麼被執行 第七次以上 06/22 11:59
→ HuangJC: 大家還是在正常邏輯裡打轉,那不可能看懂我這篇啊... 06/22 12:00
→ dennisxkimo: 我看起來是0> 1> 2> 3> 4> 5> 6 七次 06/22 12:06
→ KekeMonster: 哈 對是 7 次 06/22 12:09
→ KekeMonster: 我覺得你為了呈現現象,把範例程式碼過度簡化到不會 06/22 12:11
→ KekeMonster: 有錯了吧 06/22 12:11
推 KekeMonster: 推 qrtt1 大所說的,我覺得比較像是邏輯性的錯誤造 06/22 12:16
推 KekeMonster: 成無窮迴圈,找出 root cause 吧 06/22 12:16
→ bitlife: java method的max code size是有64KB的上限,但compiler多 06/22 12:30
→ bitlife: 半會警告,4000行過了沒有難以確定,但寫這麼長確定不太好 06/22 12:30
→ bitlife: 除錯及維護 06/22 12:30
→ HuangJC: 那我再說一次,不是跑了七次,是 endless,大家還是覺得 06/22 13:05
→ HuangJC: 邏輯性錯誤? 06/22 13:05
→ HuangJC: 當跑了二十次,我們就懷疑其布林值,這時還為true就怪了 06/22 13:08
→ HuangJC: 程式有簡化,但簡化的版本足以重製問題… 06/22 13:09
→ HuangJC: 我不是在表達邏輯而已,而是就這樣的code就會有endless l 06/22 13:11
→ HuangJC: oop 06/22 13:11
→ dennisxkimo: "不結束,__result永遠為true",for loop 無關resut吧 06/22 14:08
→ ssccg: 有錯和沒錯的版本bytecode長一樣? 以你的說法看起來跟build 06/22 16:00
→ ssccg: 環境有關,跟執行環境沒關? 06/22 16:01
→ ssccg: 那去研究stack memory好像方向錯誤 06/22 16:03
→ HuangJC: For loop 是否結束,決定於那個布林算式,那是和result一 06/22 16:17
→ HuangJC: 模一樣的算式 06/22 16:17
我文章從頭到尾都說和build machine 有關啊,換一台就不會
從前我做 embedded system 時
用的 cross compiler 很讓人懷念
那是一個只要一模一樣的 source code 進去
build 出來的 bin 就會一模一樣的 compiler
因此,每當我拿到一台新的電腦,重架起 compiler 時
第一件事,就是 build 一下我們最新的 release
拿到結果後做 file compare, 一定要一模一樣
若沒一模一樣,就一定有錯
又比如,加入新 model, 新功能
而同一個 code 要 build 舊 model
code 已經有變了,要證明沒影響舊 model
也是 build 一版出來做 file compare
像現在這個問題,特定 build machine 有問題
其實我是很想說"就那台 build machine 自己有問題啦"
但不只一台會
因此如果沒找到問題,有個合理假設
換台機器 build 仍然只會被認為是頭痛醫頭腳痛醫腳而已
※ 編輯: HuangJC (60.251.197.55), 06/22/2016 16:34:28
→ bitlife: 作業系統,CPU,JDK版本等都一模一樣嗎? 這當然有可能是遇 06/22 16:37
→ bitlife: compiler本身的錯誤 06/22 16:38
__lCurrentTime = 1466154837;
__lTimeout = __lCurrentTime + 6
跑數次後, __lTimeout 沒變
__lCurrentTime 變成 1446154850
然後 __result = __lCurrentTime<=__lTimeout;
這個 __result 依然是 true
這樣大家感覺到問題沒有?
當然我自己不會這樣寫程式
我認為如果是記憶體錯亂,那麼 for loop 裡的布林值和算式裡的布林值,就未必結果相同
真的要龜毛,必需這樣寫
for ( ; __result = (__lCurrentTime<=__lTimeout) ; __lCurrentTime++) {
Log.v(TAG, __result);
__count++;
}
也就是一定要和 forloop 的判斷式,用同一個
看到底是 boolean 不如預期
還是明明為 false 了但卻不往外跳
不管哪一個,其實都是執行不如預期
但我覺得這樣會更精準點
這已經不是語言邏輯層次的問題,我其實見過很多次
文章前面也說了:當記憶體配置出問題,那就見怪不怪了
java 用的是 GC,而不像 C 要由工程師自己去管理記憶體,new & delete
因此在 GC 極強的記憶體管理下
我其實還不太清楚怎麼造出以前的狀況
在 C,這種問題可以說是層出不窮,都是要去檢討有沒有做了違犯規定的存取..
※ 編輯: HuangJC (60.251.197.55), 06/22/2016 16:45:12
→ ssccg: 所以bytecode到底一不一樣... 06/22 16:45
→ HuangJC: bytecode 是指 build 出來的結果嗎?不一樣 06/22 16:45
→ ssccg: compiler有可能就真的有bug啊... 06/22 16:45
→ HuangJC: android compiler 可以做到一模一樣嗎?我試試 06/22 16:46
→ ssccg: android build過程有幾個step,至少先看一下是從.class就不 06/22 16:50
→ ssccg: 一樣,還是dex不一樣,還是哪邊...才知道問題在哪步吧 06/22 16:50
→ HuangJC: 我自己的電腦 build 兩次,就不會一模一樣了 06/22 16:53
→ HuangJC: 所以這種 compiler 是無法這樣驗證的 06/22 16:53
推 kiwatami: 我覺得問題不在機器 而是程式裡面有不夠嚴謹的判斷 06/22 16:58
→ kiwatami: 而機器的差異導致這個 bug 產生 06/22 16:58
→ kiwatami: 有沒有試著在迴圈內 print 相關的值? 06/22 16:58
→ kiwatami: 看看值的變化在兩台機器上有什麼不同? 06/22 16:58
→ kiwatami: 並且確定每次值的異動是不是都有執行到 06/22 16:58
→ kiwatami: 感覺你的判斷式跟時間有關 如果加上 thread.sleep 06/22 16:58
→ kiwatami: 有沒有可能導致結果不如預期? 06/22 16:58
→ kiwatami: 例如時間差太大跑進了其他判斷式 導致 r 或 c 沒被更新 06/22 16:58
→ kiwatami: 然後這個迴圈本來就會執行七次 r 也會是 true 06/22 16:58
→ kiwatami: 如果 r 不是 true 就根本進不去迴圈了 06/22 16:58
→ kiwatami: 你仔細看 迴圈的條件跟你的 r 值條件是一樣的 06/22 16:58
→ HuangJC: 為什麼和時間有關?又沒有 time api.. 06/22 17:07
→ ssccg: 最後的apk當然會不一樣,但是.class和.dex我試過同電腦相同 06/22 17:09
→ dennisxkimo: for結束跳出後 {}內result還是true很正常 06/22 17:11
沒結束,沒跳出;我再強調一次,這變成 endless loop
→ HuangJC: ... 謝謝大家,因為問題已解,所以我也重製不了了... 06/22 17:11
※ 編輯: HuangJC (60.251.197.55), 06/22/2016 17:12:08
→ dennisxkimo: __result不能拿來判斷迴圈結束條件的狀態 06/22 17:12
→ HuangJC: 但如果迴圈死不結束,它就有意義了 06/22 17:14
→ dennisxkimo: 死不結束 要檢查你結束條件設的變數 不是result 06/22 17:19
可是 result 和結束條件一模一樣啊
> → kiwatami: 你仔細看 迴圈的條件跟你的 r 值條件是一樣的
是的,我知道;故意的
就是一模一樣,所以我們現在在這裡各說各話了...
※ 編輯: HuangJC (60.251.197.55), 06/22/2016 17:22:41
→ dennisxkimo: for條件內的A<=B 跟{}內的C=A<=B的C不同 06/22 17:21
是的,你和我一樣龜毛
所以如果讓我主導,我主張這樣寫
for ( ; __result = (__lCurrentTime<=__lTimeout) ; __lCurrentTime++) {
分成兩行,就要小心有別的機會被修改變數
我再申明一點:
這不是示意 code, 我們就只把這麼簡單的 code 放在程式裡
就變成 endless loop
所以誰還能偷改變數?
邏輯內已經沒什麼好檢討,要檢討的是邏輯外...
※ 編輯: HuangJC (60.251.197.55), 06/22/2016 17:25:30
→ HuangJC: 還有一點,因為"一定要特定機器 build 才會出錯" 06/22 17:28
→ HuangJC: 所以,無法在自己機器上步進執行.. 06/22 17:28
→ HuangJC: 只能不斷的設計 log;所有變數,我們都監看了 06/22 17:28
→ HuangJC: 當我們 trace 到,current 值'遠'大於 timeout值時 06/22 17:29
→ HuangJC: 真的,無法解釋.. 06/22 17:29
→ HuangJC: unsigned 的溢位我也假設過;所以我們才把布林值記下來 06/22 17:30
→ HuangJC: 不然不必加上那一行的.. 06/22 17:30
推 dennisxkimo: 不要被改結束條件就for(int i=0;i<=6;i++){},不行嗎? 06/22 17:33
行啊,但那就不是我們的程式了
同事只說"這片段可以重製 BUG"
我們是要找問題
最後,我們同意這是堆疊爆了的現象
他說完後,誰讚成,誰反對?
→ dennisxkimo: 不然當curtime某原因不小心大於long.max就... 06/22 17:34
所以我們的實驗方法是有注意到這點的
※ 編輯: HuangJC (60.251.197.55), 06/22/2016 17:36:38
→ KekeMonster: " 分成兩行,就要小心有別的機會被修改變數" 寫成一 06/22 17:36
→ KekeMonster: 行就沒機會? 你程式最後被編譯成 bytecode 後變成幾 06/22 17:36
→ KekeMonster: 個指令? 06/22 17:36
→ HuangJC: 盡人事而已,我沒更好的工具了.. 06/22 17:37
→ dennisxkimo: 假設Timeout保證不變,應該是觀察__lCurrentTime值 06/22 17:37
有 log 它沒錯
→ HuangJC: 我們對 bytecode 會有的現象真的不熟.. 06/22 17:37
※ 編輯: HuangJC (60.251.197.55), 06/22/2016 17:38:02
題外話,在以前寫 C 時,我碰過另一個靈異現象
void F2() { a = 5; }
void F1() {
F2();
a = 6;
a = 7;
.
.
.
}
如上,F2 是幫忙設一些初始值
接下去 F1 的片段是開始修改那些值
可是我發現那些值一直跳回來
比如上例的 a, 明明後續設為 6, 設為 7
但監控它會發現它一直是 5
我全域搜尋 a 何時被改為 5,只有 F2 裡有做這件事
後來忍不住了,在 F2 開頭下一個中斷點
結果發現,F1 "每執行一行",就會跳去執行 F2 一次
程式裡完全看不出來,但執行結果就是這樣
那次我加班到凌晨都解不掉
幸好第二天,主管說那個案子不做了
我真的感覺莫名其妙
不管重開機,clean build
種種能把環境弄乾淨的方法我都試了
但我就是不知道,誰奪取了控制權,硬去執行 F2 的
那種感覺就像陷入了單步中斷
debugger 就會做這種事
但那是 VC 的特權,誰搶去做了?
※ 編輯: HuangJC (60.251.197.55), 06/22/2016 17:49:37
→ dennisxkimo: 你是用debug 一一觀察每次迴圈 各變數變化嗎? 06/22 17:59
→ dennisxkimo: 還是在迴圈內外插入print之類來看數值? 06/22 18:00
這要講兩支程式,一支是線上實際出貨的
這個有看所有變數
另外一個是為了縮小測試範圍,所以簡化(但仍然可以重製 bug)
公開在板上的是這支程式
這支程式裡已經沒有 log 了
我也不可能用步進執行
因為,一但步進執行,就是在我的機器上重 build
(不重 build 但又可以步進執行,以前 code view 玩過,現在在 android 我還沒試過)
而我們強調是'那一台特定 build machine 才會有問題'
公開的這一支,同事只說,永遠執行不到第二個中斷點
endless loop
這句話,和實際出貨的程式倒是一樣
QA 測到的就是迴圈跑不完;但比較值已經遞增到極大了,為什麼還不結束 loop?
(比較值及被比較值,兩者都有 log)
我是很想參戰啦,但我沒重製過問題
我參戰的必要條件是:讓我可以操作 build machine,任意 build 我改的 code
這點,沒法子
大家煩得要死,不給我玩
--------------
我們出貨的程式,不是任意 RD 電腦 build 出來都可以出
而是特定的 build machine
它一連串的批次動作,從取 code, build, 到包裝成出貨 package 放至特定子目錄
(子目錄名稱和時間日期相關)都是自動化的
我無法把我的 code 塞給它,除非我 check in code
他們不給我玩啦..
所以,謝謝大家,警察可以出來洗地了...
※ 編輯: HuangJC (60.251.197.55), 06/22/2016 18:15:49
推 kiwatami: 喔 因為你特別用 time millis 當範例 我才以為你很直覺 06/22 18:15
→ kiwatami: 的使用你原本的原始碼使用的判斷式當例子 才會猜你原本 06/22 18:15
→ kiwatami: 的判斷式是不是跟時間有關 06/22 18:15
→ kiwatami: 不過既然你知道是一樣的 06/22 18:15
→ kiwatami: 怎麼會認為他跑不了第七次? 06/22 18:15
→ kiwatami: 是跑不了第八次才對 06/22 18:15
→ kiwatami: 如果是你說的那個什麼堆疊爆了... 06/22 18:15
→ kiwatami: 這範例才 10 行不到 不太可能吧 06/22 18:15
但我有說,這段程式,要放在我們程式內
我不想怪你沒爬文耶,我是來請教大家的,要有禮貌 :P
→ kiwatami: print currenttime 會超過 timeout? 還是永遠等於? 06/22 18:15
會超過 :P
※ 編輯: HuangJC (60.251.197.55), 06/22/2016 18:18:22
→ ssccg: 這次沒得試了,下次遇到再說吧,我是覺得比較可能是編出來 06/22 18:35
→ ssccg: 執行檔就是錯的,不然stack爆了再把long cast成double反而 06/22 18:36
→ ssccg: 會對不太合理 06/22 18:37
→ HuangJC: 我多少表達個參戰的決心,唉 06/22 18:38
→ HuangJC: 講話要先大聲才有機會 06/22 18:38
→ HuangJC: 我很有想法,我覺得log太少了 06/22 18:39
for ( ; ; __lCurrentTime++) {
__result = (__lCurrentTime<=__lTimeout);
Log.v();//__result, __lCurrentTime, __lTimeout 三個都要看
//一次就能讓大家標定問題
if ( !__result )
break;
}
這樣更龜毛
問題應該可以限縮至"為什麼 long compare 出錯了"
※ 編輯: HuangJC (60.251.197.55), 06/22/2016 19:09:55
→ HuangJC: 我一向不喜歡同樣的事做兩次,然後預設它們結果一樣 06/22 19:10
→ HuangJC: 我喜歡的 code style,就是只做一次,把它存起來用兩次 06/22 19:10
→ HuangJC: 這樣爭議小很多.. 06/22 19:11
→ HuangJC: 比如 multi-thread 變數瞬變,我踢的鐵板也夠多了.. 06/22 19:12
推 kiwatami: 我沒爬完整 我錯了 我自己去角落玩沙... 06/22 19:51
→ HuangJC: :P 我自己安全過關就好.. 06/23 00:14
→ realmeat: c compile出問題並不是啥新鮮事 06/23 11:45
→ realmeat: 通常都要追bytecode才看的出來 06/23 11:45
→ HuangJC: byte code 要怎樣追?以前寫 VC 時,可以輕易調出組合語 06/23 12:41
→ HuangJC: 言,以及監看所有暫存器;現在在 Android Studio 下,也 06/23 12:41
→ HuangJC: 可以輕易看到 bytecode 嗎? 06/23 12:41
→ Chikei: 拿artifact出來直接看編出來的bytecode不同機器是不是一樣 06/24 14:56
→ eieio: 1446154850 本來就比 1466154837+6 要小啊 07/01 12:41