Строка crontab без наложений
Запуск, который пришёлся на работающий прошлый, тихо пропускается.
flock держит блокировку файла, пока работает команда: второй экземпляр скрипта ждёт её или сразу выходит.
flock -n /run/lock/clean.lock clean.sh
занято — выйти с кодом 1
flock -w 30 /run/lock/clean.lock clean.sh
ждать до 30 секунд
flock /run/lock/clean.lock clean.sh
ждать сколько нужно
flock -n -E 75 /run/lock/clean.lock cmd
занято — код 75
flock -n -o /run/lock/app.lock cmd
потомки не держат блокировку
lslocks -o COMMAND,PID,TYPE,PATH
кто держит блокировки
Скрипт по расписанию может стартовать, пока прошлый запуск ещё работает: очистка затянулась, кто-то запустил её руками, задача записана и в cron, и в таймере. Два экземпляра удаляют одни и те же файлы и пишут в один журнал вперемешку.
flock открывает файл блокировки, берёт на него
блокировку у ядра и только потом запускает команду. Пока
команда работает, блокировка занята. Второй flock на тот же файл ждёт или, с
ключом -n, сразу выходит с кодом 1. Сообщения он при этом не печатает:
Получив блокировку, flock возвращает код самой команды.
| Как вызван | Блокировка занята | Код, если не дождался |
|---|---|---|
flock FILE cmd |
ждёт освобождения | — |
flock -n FILE cmd |
сразу выходит | 1 или число из -E |
flock -w 30 FILE cmd |
ждёт до 30 секунд | 1 или число из -E |
Блокировку держит открытый файловый дескриптор.
Процесс завершился любым способом, даже от kill -9, — ядро закрыло
дескриптор и сняло блокировку. Поэтому после сбоя блокировка не «зависает».
Сам файл остаётся на диске, удалять его не нужно.
Блокировка рекомендательная: ядро не запрещает писать в файл и не трогает
программы, которые flock не вызывают. Защита работает, когда каждый запуск
берёт блокировку на одном и том же файле.
Файл блокировки удобно держать в /run/lock (он же /var/lock): каталог в
памяти, писать в него может любой пользователь, после перезагрузки он пуст.
Основные ключи — в таблице ниже. Полный список — man flock.
| Ключ | Что делает | Пример |
|---|---|---|
-n --nonblock |
Не ждать; блокировка занята — выйти с кодом 1 без сообщения. | flock -n /run/lock/clean.lock clean.sh |
-w --timeout SECONDS |
Ждать блокировку не дольше заданного числа секунд, потом код 1. | flock -w 60 /run/lock/backup.lock backup.sh |
-E --conflict-exit-code N |
Код выхода, если блокировку не получили (вместо 1). | flock -n -E 75 /run/lock/x.lock cmd |
-o --close |
Закрыть дескриптор перед запуском команды — потомки не держат блокировку. | flock -n -o /run/lock/app.lock app-start.sh |
-c --command STRING |
Выполнить строку оболочкой — для нескольких команд под одной блокировкой. | flock -n /run/lock/x.lock -c 'step1 && step2' |
FD |
Блокировка на открытый дескриптор скрипта вместо файла и команды. | exec 9>/run/lock/x.lock; flock -n 9 |
-o --output LIST |
Какие столбцы показать в lslocks; PATH — файл блокировки целиком. | lslocks -o COMMAND,PID,TYPE,PATH |
-s --shared |
Общая блокировка — её могут держать несколько читателей сразу. | flock -s /run/lock/data.lock cat data.csv |
-x --exclusive |
Исключительная блокировка; так flock работает по умолчанию. | flock -x /run/lock/data.lock update.sh |
-u --unlock FD |
Снять блокировку с дескриптора раньше выхода скрипта. | flock -u 9 |
Ничего не найдено. Попробуй запрос короче.
Запуск, который пришёлся на работающий прошлый, тихо пропускается.
exec 9>файл открывает дескриптор 9 для всего скрипта, flock -n 9 берёт на
него блокировку. Скрипт защищён, как его ни запусти: руками, из cron или
таймером. Код при занятой блокировке выбери сам: 1 — попытка видна как сбой в
systemctl status и мониторинге, 0 — пропуск считается нормой.
Второй экземпляр того же юнита systemd и сам не запустит: пока служба
работает, повторный systemctl start присоединяется к текущему запуску.
flock нужен, когда тот же скрипт запускают ещё и руками, из cron или другим
юнитом. Расписание — в systemd-timers.
lsof показывает и процесс-потомок: sleep получил дескриптор от flock и
тоже держит блокировку.
Файл блокировки нельзя открыть. Частый случай: student однажды запустил скрипт
руками, файл в /run/lock создан от него, и теперь служба от root получает
отказ — в общем каталоге с битом sticky Ubuntu не даёт root открыть чужой файл
(fs.protected_regular = 2). flock выходит с кодом 66:
Удали файл, пока его никто не держит, и запускай скрипт от одного
пользователя. Службе от root можно отдать свой путь: /run/clean.lock.
Так работает -n: занято — код 1 без сообщения. Чтобы пропуск был виден в
журнале, допиши || echo "пропуск: прошлый запуск ещё идёт" >&2 или пиши
сообщение в скрипте, как в рецепте выше.
flock не нашёл команду (код 69). В cron и службах PATH короче, чем в
терминале, — пиши полный путь: /usr/local/bin/clean.sh.
Запуски берут блокировку на разных файлах или один из них идёт мимо flock.
Сверь путь во всех местах: crontab, ExecStart=, сам скрипт. /var/lock и
/run/lock — один каталог.
Пока первый экземпляр держит блокировку, rm убирает файл из каталога.
Следующий flock создаёт новый файл и спокойно берёт блокировку на нём — два
экземпляра работают одновременно:
Не удаляй файл блокировки — ни руками, ни в конце скрипта.
Дескриптор наследуют все процессы, запущенные командой. Если она оставила
процесс в фоне, блокировка живёт, пока жив он, — даже после kill -9 самого
flock. Ключ -o закрывает дескриптор перед запуском команды: блокировку
держит только flock.
set -e.[ -f lock ] не защищает от двойного запуска.Есть неточность? Сообщить об ошибке в статье