天天看點

記一次目前工作目錄問題的排查經曆

最近在使用clearcase的時候遇到一個問題,當從指令行裡啟動版本樹,并想給一個節點打上review屬性時,經常會出現一個指令視窗一閃而過,重新整理版本樹之後卻沒能找到想要打的review屬性,隻有再次嘗試才會正确打上。大家忍受了這個問題很久,但一直都沒時間去深入分析它。在連續幾次遇到這情況之後,我覺得忍無可忍,下定決心解決它,最終找到了問題的根源并給出了解決方案,在這裡詳細記錄一下這次排查的經曆。

clearcase資料總管,和windows資料總管很相似

指令行,直接輸入<code>clearvtree file_path</code>

因為流程方面的要求,在每次送出一份代碼後,必須經過相應的單元測試,由送出者打上<code>unittested</code>屬性,然後交給他人review,如果沒問題,他會打上<code>reviewed</code>屬性,否則送出者需要再重複這一過程隻到問題解決為止。隻有在這兩份屬性共同存在的情況下,新版本才能被允許進入build中,這從很大程度上保證了代碼的品質。

然後在windows的<code>sendto</code>目錄下建立一個快捷方式<code>reviewed</code>,指向這個腳本。于是,要給一個檔案建立屬性時,隻需:

用前面介紹的任一種方式啟動版本樹

在版本樹中對目标節點點選右鍵,然後在<code>sendto</code>菜單下選擇建立的<code>reviewed</code>選項

一切都顯得很正确,但用了一段時間之後,很多人發現一個現象,打開版本樹後,經常點了<code>reviewed</code>選項,發現一個指令視窗一閃而過,重新整理卻看不到剛建立的屬性,然後隻有再試一次才能成功,更為奇怪的是這個問題并不總是出現。

要解決這個問題,這裡有三點需要解答:

為什麼問題不是每次都出現?

為什麼第一次沒有成功,出了什麼錯誤?

為什麼重新整理一次之後就可以了?

這裡首先看看第一次到底出了什麼錯誤。由于指令視窗一閃而過,無從知道發生了什麼,是以要麼重定向腳本,要麼讓腳本執行完之後停下,而不是關閉退出,這樣才能夠得到錯誤資訊,這裡采用了簡單的暫停腳本方案。找到腳本檔案,在最後加上<code>pause</code>,再試一次建立屬性,得到了錯誤資訊(注:這裡假設檔案是:<code>q:\project\src\test.cpp</code>,版本是<code>main\7</code>,是以在clearcase裡面整個檔案的路徑為<code>q:\project\src\test.cpp@@\main\7</code>):

找不到檔案?在指令行中運作:

明明檔案存在,怎麼會顯示找不到這個檔案呢?

想到之前出現過在clearcase中無法找到檔案的情況:通路大量檔案時偶爾會出現無法找到個别檔案的情況,那是由于網絡的問題。因為clearcase采用的是集中式的版本控制,我們建立view的方式是<code>dynamic</code>,而不是<code>snapshot</code>,是以所有的資料實際上存在于clearcase伺服器上,用戶端想要通路一個檔案時,會通過網絡協定去伺服器擷取,取回本地之後才能夠通路。如果出現大量的檔案通路,網絡又不是特别好的情況下,就可能會出現傳輸失敗,進而無法通路檔案的情況。

于是用<code>ccadminconsole.msc</code>指令去查找clearcase所有的log,結果真在<code>clearcase\my host\server logs\view</code>下找到了一些可疑的log:

難道是這個原因?去網上搜尋一陣也無法得到有用的資訊。

但是轉眼又想,既然剛剛是由于這個檔案還沒有取回本地導緻的,如果我再次打開版本樹,并嘗試建立<code>review</code>屬性,是否就應該沒問題了?抱着這種想法又試了一次,結果還是和剛才情況一樣,無法找到檔案,于是否定了這個猜測。

放棄了上面的猜測之後,又進行另一種猜測,會不會是<code>sendto</code>的機制有些沒弄明白的地方?如果不用<code>sendto</code>的這種方式,而直接用前面介紹的指令,會有一樣的結果麼?于是在指令行中調用:

竟然成功執行,那麼問題可能就出現在<code>sendto</code>上。接着猜想,難道是由于<code>sendto</code>的實作機制有問題?如果不用<code>sendto</code>,而是直接在指令行裡調用這個腳本可以麼?于是把上面的指令儲存成一個腳本,放在<code>c:\test\reviewed_by_me.bat</code>,再調用一次:

仍然成功。

等等,這和<code>sendto</code>的方式還有一點差別,<code>sendto</code>是的确是調用了這個腳本,但是它是通過一個快捷方式來調用的,而不是直接運作腳本。為了達到一樣的實驗條件,我也建立一個快捷方式:<code>reviewed</code>,指向上述腳本。輕按兩下之後,發現果然出錯了,一樣的無法找到檔案!

現在的問題就變成了輕按兩下以快捷方式打開的腳本和直接從指令行裡啟動的腳本有什麼差別?也許大家看到這裡就能猜到原因了,但是當時我還沒有立刻意識到,而是同時思考了另外一個問題,為什麼把版本樹重新整理一次又可以了呢?重新整理前後都調用的是同樣的<code>sendto</code>,這次重新整理前後有些什麼差別?

為了得到更多的資訊,将<code>reviewed_by_me.bat</code>中的指令執行前都列印輸出,執行兩次之後注意到了問題所在。這是第一次失敗時的結果:

這是重新整理之後成功執行的結果:

細心的你可能已經發現了這個差別,成功的那一次多了一個q:\,是一個絕對路徑,失敗的是相對路徑,難道問題就出在這裡?那為什麼前面直接在指令行中用這個相對路徑也能正确執行呢?

這個路徑是怎樣來的?

在前面調用<code>clearvtree</code>時用的是:

難道是這個原因?于是我試了一次輸入絕對路徑給<code>clearvtree</code>:

然後再建立一次<code>review</code>屬性,真的成功了!

通過前面的猜測,現在的問題就定位在相對路徑與絕對路徑上,文章剛開始的三個問題變成:

為什麼相對路徑會出錯?

為什麼同樣是相對路徑,在指令中的調用不出錯,而通過<code>sendto</code>就出錯了?

為什麼重新整理之後相對路徑變絕對路徑了?

我們知道任意一個程序在處理一個相對路徑時,為了正确的通路檔案,都需要另一個重要的參數:<code>目前工作目錄</code>,程序,更準确的說是作業系統會将相對路徑擴充成一個絕對路徑再進行處理:

前面都是我們的推測,到了該印證推測的時候了。

從指令行中的調用和<code>sendto</code>的方式結果不一緻入手。 由于它們的相對路徑一樣,那麼問題一定是出在<code>目前工作目錄</code>上,先來看指令行的方式,從頭到尾我隻開了一個指令行視窗,它的工作路徑是:<code>q:\</code>,是以,在這裡面調用的子程序預設都會繼承同樣的<code>目前工作目錄</code>,是以傳給它們的相對路徑最終都會擴充成為正确的路徑:

再來看<code>sendto</code>,因為這裡調用的是一個<code>快捷方式</code>,它有一個特點,可以指定自己的<code>start in</code>參數,下圖是<code>sendto</code>中<code>reviewed</code>快捷方式的屬性:

記一次目前工作目錄問題的排查經曆

這裡的<code>start in</code>屬性指定的就是調用指令時的<code>目前工作目錄</code>,它的值是<code>m:\admin\tools\review</code>,是以一個相對路徑擴充之後變成:

這個路徑當然是錯誤的,這就可以解釋為何<code>sendto</code>的方式會出錯了。

前面介紹過打開版本樹通常有兩種方式,除了剛剛讨論的指令行,還可以從<code>clearcase資料總管</code>中打開,這種情況不存在我們讨論的問題,因為點選打開版本樹選項之後,資料總管直接将完整的路徑傳給了<code>clearvtree</code>程序,是以不管它的目前工作目錄位于何處,都可以正确的處理。這也可以解釋文章最開始提出的問題:為什麼這個問題有時出現,有時不出現?

對于問題3,沒有找到相關的資料,推測可能是由于重新整理之後,<code>clearvtree</code>程序内部将路徑擴充,這是程式内部實作的問題,在這裡不作讨論。

找到問題之後,應該如何解決呢?想到兩種解決思路:

把<code>目前工作目錄</code>設定成與<code>clearvtree</code>一樣

在<code>reviewed_by_me.bat</code>腳本中将相對路徑擴充成絕對路徑

對于方案二,打算掃描每個view,然後去比對路徑,但這樣速度一定很慢,而且還無法保證正确性,放棄。

那麼隻有采用方案一,現在的問題變成,如何獲得<code>clearvtree</code>的目前路徑?在<code>reviewed</code>調用腳本的時候,隻能從<code>clearvtree</code>那裡獲得檔案的相對路徑,存儲在<code>%1</code>中。工作目錄從哪裡擷取到呢?

在前面讨論中,一個程序調用子程序時,預設情況下子程序的<code>目前工作目錄</code>會與父程序一樣,什麼是預設情況?其實就是子程序沒有明确指定自己<code>目前工作目錄</code>的情況,而這裡的<code>reviewed</code>快捷方式是設定了<code>start in</code>,這樣就意味着如果将它清空,腳本便會從<code>clearvtree</code>中獲得正确的<code>目前工作目錄</code>,根據這個分析開始驗證:

清空<code>reviewed</code>快捷方式的<code>start in</code>

在<code>reviewed_by_me.bat</code>腳本中輸出<code>目前工作目錄</code>: <code>for /f %%i in ('cd') do echo %%i</code>

本來期盼着得到:<code>q:\</code>,結果卻出乎意料,輸出的是:

這根本不是一個目錄,不知為何<code>clearvtree</code>将腳本的工作路徑設定成了這樣一個奇怪的”目錄”。但是不管怎麼樣,起碼我們可以得到一個大概的路徑,知道它在<code>q:\</code>盤下,現在的相對路徑是:

可以從前面的<code>目前工作目錄</code>中得到盤符,然後去猜測相對路徑,或者拿相對路徑與<code>目前工作路徑</code>去比對,計算得到一個正确的路徑……

這種方法可以得到正确的結果,但是有些複雜,最終沒有采用,因為發現了一種更為簡單的<code>workaround</code>。

上面的奇怪”目錄”是怎麼來的,注意看該節點完整的路徑:

對比可以發現,其實是由于作業系統不知道<code>clearcase</code>采用的檔案命名方式:

作業系統将上面<code>clearcase</code>内部路徑當成了一個普通的檔案路徑,<code>版本号7</code>被當成了檔案名,而從右邊開始的第一個<code>版本号分隔符\</code>被當成了<code>路徑分隔符</code>,作業系統去除”檔案名”,是以才出現了這種奇怪的”目錄”。

為了印證這一點,我又對另外一個節點建立<code>reviewed</code>屬性:

這是在<code>main</code>上的一個<code>test</code>分支,再次選擇<code>sendto</code>下的<code>reviewed</code>快捷方式,得到下面的<code>目前工作目錄</code>:

看到這裡想必大家都明白我要做什麼了,沒錯,這個奇怪的”目錄”其實就差一個<code>版本号</code>就可以構成一個完整的節點路徑,而<code>clearvtree</code>傳過來的<code>%1</code>參數就包含了這個<code>版本号</code>,截取之後拼在上面的目錄後就可以得到完整的路徑,這裡是最終的<code>reviewed_by_me.bat</code>代碼:

(2014.06.06更新)

因為有時并不是隻對已經archive的節點進行review和unittest,而是會直接操作于checkout檔案。那麼此時便沒有了上面的<code>版本号</code>一說,直接使用上面的代碼會出現問題。還是用上面的例子來說明:

此時,假設test.cpp是checkout的檔案,我們想對其增加review和unittest屬性。這裡得到的目前工作目錄是:

路徑完全正确,沒有了前面<code>@@main</code>引起的幹擾。是以檔案的完整路徑就可以這樣得到:

但是如果直接用前一節的<code>reviewed_by_me.bat</code>腳本,卻會得到這樣的結果:

這裡少了字尾名,為什麼呢?問題出在代碼中的<code>set version_num=%%~ni</code>,因為<code>%~ni</code>隻表示檔案名,是以隻有<code>test</code>出現。如果想要取得完整的路徑,那麼隻用再加上字尾名即可,即将<code>set version_num=%%~ni</code>改成<code>set version_num=%%~nxi</code>,僅僅增加一個<code>x</code>。因為這種情況下字尾名為空,是以也不會影響前面說的archive檔案。

是以,最終版本的<code>reviewed_by_me.bat</code>代碼變為:

(2014.08.22更新)

因為有時會不使用<code>sendto</code>快捷方式來打标簽,而是直接調用腳本,此時,目前工作目錄可能會出錯,為了避免這種情況,腳本又做了以下更新:

測試亦作了相應的更新。

最後對下面幾種方式啟動的版本樹進行了測試,所有的情況都成功的運作:

至此,該問題成功解決。

這是一次平常衆多工作中遇到的一個細小的問題,通過一點點的排除,順藤摸瓜,最終找到了問題的根源。這篇文章非常詳盡(啰嗦似乎更合适)的通過自問自答的方式,記錄了整個分析過程。對我來說,重要的不是解決了這個問題,而是訓練自己遇到事情不去忍受,想辦法解決的意識。

記得剛剛參加工作時,由于要分析程式崩潰的原因,用一個指令每次一行的分析backtrace檔案,通過記憶體位址去得到堆棧的确切位置。每次都需要拷貝位址,修改指令參數,執行,通常要執行五六次以上才能夠找到有用的資訊。但當時好像每個人都認同這種方式,不就是簡單的幾步操作麼,沒覺得有多麻煩啊,我也一樣。後來一個哥們寫了一個簡單的shell腳本,輸入為backtrace檔案,一次把所有的記憶體位址轉換成檔案的确切位置,看到這個腳本時立刻被震撼到了,不是這個腳本有多複雜,而是除了他,沒有任何人想到這點,所有人都在忍受,甚至連忍受都感受不到,麻木的接受一切。

其實每一份忍受都源自于對現狀的不滿足,各行各業存在的目的不就是為了解決人們各種各樣的不滿足麼,一個公司能否持續發展不也是看它能否不斷發現并滿足人們的不滿足麼?換種說法,這種不滿足也就是需求。面對需求我們應該做些什麼?發現并抓住它,有可能就成為機遇;忽略它,可能就變成了抱怨。人的追求是無止盡的,現狀永遠都滿足不了人,是以我一直認為,隻要存在不滿足的地方,就一定存在着機遇,問題在于我們怎樣面對它們。我們上司經常說這樣一句話:别抱怨,提建議;别建議,解決它。我很喜歡這句話,送給大家,與君共勉,做一個有心人。

(全文完)

繼續閱讀