背景
碰到一个偶现的编译出错问题, 如图
报错的信息是
cp: 无法创建普通文件 "xxx": 文件已存在
排查原因
看了下 Makefile, 这句非常简单, 就是 cp ./xxx ../xxx 而已, 本身没什么问题.
那再结合上下文出现的打印, 一个异常之处就是 Makfeile 被并行重复执行了, 猜测是并行导致 cp 操作出错.
只考虑解决问题, 那无疑是修改外层 Makefile , 避免此处被并行重复执行, 至少这句 cp 不要被并行, 就可以解决了.
但为什么 cp 并行执行会出错呢? 如果在另外的场景下确实有并行执行 cp 的可能, 有没有办法规避这个错误呢? 这就得探究下了.
单独执行 cp, 默认的行为就是覆盖已存在的文件, 并不会因为 "文件已存在" 这样的原因出错, 随便做下实验, touch a b; cp a b 就可以确认正常是不会报错的.
那问题还是得结合并行来分析, 碰到这种情况, 要么是从搜索资料获得提示, 要么就是实践出真知, 自己设计一个可快速复现的方式, 然后使用调试工具来追踪问题发生时的具体情况.
具体到这个问题, 我是搜索到相同的问题, 那就省点工夫不用自己去复现分析了.
这里插下题外话, 搜索优先使用 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
来源: https://www.cnblogs.com/zqb-all/p/12942556.html