我有一个通过 cron 定期(每隔几分钟)执行的脚本。然而,该脚本不应该并行运行多次,并且有时运行时间会更长一些,因此我想实现一些锁定,即确保如果先前的实例已经在运行,则脚本会提前终止。
根据各种建议,我有一个如下所示的锁定:
lock="/run/$(basename "$0").lock"
exec {fd}<>"$lock"
flock -n $fd || exit 1
如果脚本的另一个实例仍在运行,这应该调用 exit 1。
现在的问题是:有时即使脚本已经终止,陈旧的锁仍然存在。这实际上意味着 cron 永远不会再次执行(直到下次重新启动或删除锁定的文件),这当然不是我想要的。
我发现 lslocks 命令可以列出现有的文件锁。它显示了这一点:
(unknown) 2732 FLOCK WRITE 0 0 0 /run...
该进程(本例中为 2732)不再存在(例如在 ps aux 中)。我也不清楚为什么它不显示完整的文件名(即仅 /run...)。 lslocks 有一个参数 --notrucate ,这对我来说听起来可能会避免截断文件名,但这不会改变输出,它仍然是 /run...
所以我有多个问题:
- 为什么会有这些锁,什么情况会导致集群中的锁在进程的生命周期之外存在?
- 为什么 lslocks 不显示完整路径/文件名?
- 有什么好方法可以避免这种情况并使脚本中的锁定更加稳健?
- 有什么方法可以在不重新启动的情况下清理陈旧的锁吗?
答案1
锁flock
与文件描述对象相关联;一旦所有引用文件描述的文件描述符都被关闭,它就会消失(参见the foll.2 联机帮助页)。
如果文件仍然被锁定,那么几乎可以肯定文件描述符仍然被原始进程或子进程引用(假设您没有使用文件描述符传递之类的东西来在原始进程层次结构之外传播对它的引用) 。
我建议检查一下sudo fuser $lock_path
。
要解决这个问题,我知道有两种方法:要么阻止 shell 让子进程继承文件描述符,要么杀死所有仍在引用它的进程,例如使用fuser -k ...
.
您看到的路径不完整,因为lslocks
用于/proc/locks
收集信息;该文件包含挂载点的标识符以及获取锁定的进程的信息,但不包含锁定文件的路径。如果lslocks
在检查该进程时找不到持有锁的文件描述符,它将回退到仅打印安装点。
答案2
我在羊群中也遇到了同样的问题。 thejh 使用 fusion 的建议帮助我找到了问题所在。事实证明,我用集群运行的命令启动了一个保留在后台的子进程。因此,即使原始命令完成,flock 也不会解锁该文件,因为子进程持有该锁。
解决方案:flock --close
“manflock”说 --close 将“在执行命令之前关闭持有锁的文件描述符。如果命令生成一个不应持有锁的子进程,这非常有用。”
这完全解决了我的问题。
答案3
我现在已经通过使用一种完全不同的方式来确保脚本只运行一次来解决这个问题。这并没有回答我原来的问题,但我会在这里分享,以防对其他人有帮助:
我现在正在使用 pgrep 检查有多少进程正在以相同的名称运行。 Twitter 上已经向我指出了这种可能性。我认为这种方法唯一可能的缺点是,如果您有多个同名脚本,它会产生干扰。但是可以通过使用足够具体的脚本名称来避免这种情况。
这是我正在使用的代码:
PNAME="$(basename "$0")"
if [[ "$(pgrep -c -u $USER $PNAME )" -ne 1 ]]; then
exit 1
fi