背景
碰到一個偶現的編譯出錯問題,如圖
報錯的資訊是
cp: 無法建立普通檔案"xxx": 檔案已存在
排查原因
看了下 Makefile
,這句非常簡單,就是 cp ./xxx ../xxx
而已,本身沒什麼問題。
那再結合上下文出現的列印,一個異常之處就是 Makfeile
被並行重複執行了,猜測是並行導致 cp
操作出錯。
只考慮解決問題,那無疑是修改外層 Makefile
,避免此處被並行重複執行,至少這句 cp
不要被並行,就可以解決了。
但為什麼 cp
並行執行會出錯呢?如果在另外的場景下確實有並行執行cp
的可能,有沒有辦法規避這個錯誤呢?這就得探究下了。
單獨執行 cp
,預設的行為就是覆蓋已存在的檔案,並不會因為 “檔案已存在” 這樣的原因出錯,隨便做下實驗,touch a b; cp a b
就可以確認正常是不會報錯的。
那問題還是得結合並行來分析,碰到這種情況,要麼是從搜尋資料獲得提示,要麼就是實踐出真知,自己設計一個可快速復現的方式,然後使用除錯工具來追蹤問題發生時的具體情況。
具體到這個問題,我是搜尋到相同的stackexchange問題,那就省點工夫不用自己去復現分析了。
這裡插下題外話,搜尋優先使用google
,對於中文報錯資訊查不到的可改成英文查詢。例如中文的 cp: 無法建立普通檔案 檔案已存在
就不好找到答案,換成 cp cannot create regular file file exists
就好找了。(只敲一部分,搜尋引擎就能提示完整的資訊)
stackexchage
上給出了一個指令碼,用於復現問題並使用 strace
將追蹤的系統呼叫記錄下來
#!/bin/bash
touch a
f() {
while true; do
rm -f b
strace -o /tmp/cp${BASHPID}.trace cp a b || break
done
}
cleanup() {
kill -9 %1 %2
}
f &
f &
trap cleanup exit
wait
附上我自己的實驗結果,可以看出cp
的實現上,會先用stat
來判斷目標檔案b
是否存在,如果不存在則會使用 open("b", O_WRONLY|O_CREAT|O_EXCL, 0664)
來建立目標檔案並將原始檔寫入目標檔案,完成複製。
那麼如果兩個 cp
併發,就可能出現
cp1 cp2
stat判斷b不存在
stat判斷b不存在
open成功,建立檔案b
open失敗,因為此時檔案已經被cp1建立好了
從 strace
的 log
看到的就是
由於 cp
不是原子的,如果兩個 cp
剛好幾乎同時執行,則可能兩個 cp
的stat
都判斷到檔案不存在,那最終只有一個 cp
能建立檔案,另一個就失敗了。
順便看看,檔案存在和不存在的open
引數差異
解決辦法
既然兩個cp
同時執行會出錯,那就加鎖唄。
如果所有呼叫 cp
的地方都是我們可控的,那勸告鎖就足夠了,在 shell
中可以直接使用 flock
。
約定好一個檔案鎖x
, 將原來的cp a b
改成 flock x cp a b
即可。
例如正常在兩個控制檯中,執行top
是可以並行的,但如果改成執行 flock /tmp/toplock top
,那就只有控制檯1
會執行top
,控制檯2
則處於等待檔案鎖的狀態。此時若控制檯1
退出top
,則控制檯2
獲得鎖,開始執行top
。
更多檔案鎖的細節,可以看看 man flock
。
blog: https://www.cnblogs.com/zqb-all/p/12942556.html
公眾號:https://sourl.cn/S42YSr