背景
碰到一個偶現的編譯出錯問題,如圖
報錯的信息是
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