Задание systemd идёт дольше интервала таймера: будет ли вторая копия и почему следующий запуск сразу
Обмен настроен на каждые десять минут, а идёт двадцать пять. Сисадмин смотрит в top, видит вечно занятый процессор и начинает считать, сколько параллельных копий скрипта там крутится. Отвечаю сразу: ни одной лишней. Вторую копию systemd не запустит никогда. Но при этом ваш сервер и правда молотит без остановки — и это совсем другая поломка, у которой есть точная причина в устройстве таймеров и три разных способа лечения. Ниже — что реально происходит между таймером и службой, почему после завершения задание стартует мгновенно, чем OnUnitActiveSec отличается от OnUnitInactiveSec, что даёт DeferReactivation= из systemd 257 и что делать, если у вас Debian 12 и никакого 257 нет.
Вторую копию systemd не запустит — это гарантия, а не везение
Начну с главного, потому что вокруг этого больше всего мифов. В systemd таймер не «запускает скрипт». Таймер активирует юнит службы. А активация уже работающей службы — это не запуск, это ничего. Формулировка в systemd.timer(5) предельно однозначная: если юнит, который надо активировать, на момент срабатывания таймера уже активен, он не перезапускается, а просто остаётся работать. И следом там же — фраза, которую я обычно прошу перечитать вслух: концепции порождения новых экземпляров службы в этом случае не существует.
То есть механизм такой. Таймер срабатывает, ставит задание на запуск связанной службы и сам переходит в подсостояние running. Пока служба активна, таймер следующее срабатывание вообще не планирует — он ждёт уведомления о том, что служба стала inactive, и только тогда пересчитывает время. Я специально проверил это по исходнику src/core/timer.c: в состоянии running пересчёт делается исключительно по деактивации запущенного юнита. Поэтому никакой очереди пропущенных срабатываний нет: если прогон шёл час при интервале десять минут, счётчик «должен шесть раз» нигде не пишется. Проверить состояние можно глазами: у самого юнита таймера подсостояние меняется между waiting (ждёт своего часа) и running (связанная служба сейчас работает).
# пока служба работает, таймер висит в состоянии running
$ systemctl status lms-sync.timer
● lms-sync.timer - Синхронизация с учебной платформой
Loaded: loaded (/etc/systemd/system/lms-sync.timer; enabled)
Active: active (running) since Mon 2026-09-07 09:00:04 MSK
Trigger: n/a
Triggers: ● lms-sync.service
# когда служба отработала — waiting и появляется Trigger
$ systemctl list-timers lms-sync.timer
NEXT LEFT LAST PASSED UNIT
Mon 2026-09-07 09:40:00 MSK 6min Mon 2026-09-07 09:30:00 MSK 3min lms-sync.timerЕдинственное честное исключение — когда служба врёт о своём состоянии. Type=forking с неправильным PIDFile, или Type=simple, у которого ExecStart дёргает обёртку, а та уводит настоящую работу в фон и мгновенно возвращает управление. Тогда юнит переходит в inactive, хотя процесс живёт, и следующее срабатывание таймера действительно поднимет ещё одну копию. Так что если вы видите два одинаковых python-процесса — виноват не systemd, виноват ваш Type= или скрипт с nohup внутри. И отдельная классика: RemainAfterExit=yes на службе, которую дёргает повторяющийся таймер. Такая служба остаётся active навсегда после первого запуска, и таймер больше никогда ничего не сделает. В мане про это написано прямым текстом.
- Таймер активирует юнит, а не процесс. Пока служба активна, таймер ждёт её завершения и нового срабатывания не планирует.
- Очереди пропущенных срабатываний нет. Долгий прогон не превратится в пачку запусков — максимум в один немедленный.
- Состояние таймера running означает «моя служба сейчас работает», waiting — «жду следующего срабатывания».
- Дубли процессов возможны только при кривом Type= или фоновом запуске внутри скрипта.
- RemainAfterExit=yes + повторяющийся таймер = служба отработает ровно один раз за всю жизнь машины.
Откуда берётся «следующий запуск сразу после окончания»
А вот теперь настоящая беда. Календарный таймер вычисляет момент следующего срабатывания от момента предыдущего срабатывания. Не от завершения службы — от её запуска. А пересчитывает он это время в тот момент, когда служба завершилась. Пока прогон укладывается в интервал, разницы никто не замечает. Как только прогон стал длиннее интервала, вычисленное время следующего срабатывания оказывается в прошлом. А срабатывание в прошлом systemd отрабатывает немедленно. Служба закончилась — и тут же стартует снова.
Это не баг и не моя интерпретация, это дословно описано в systemd.timer(5) в разделе про DeferReactivation=: поведение по умолчанию таково, что юнит таймера немедленно срабатывает снова, как только служба завершилась; так происходит потому, что таймер планирует следующее срабатывание от предыдущего времени срабатывания, и, поскольку интервал короче времени работы службы, это срабатывание оказывается в прошлом.
Разберём по шагам на живом примере — OnCalendar=*:0/10 и прогон в 25 минут. В 09:00 таймер срабатывает, служба стартует. В 09:10 и 09:20 не происходит ничего: таймер находится в состоянии running и ждёт завершения службы, новых срабатываний он не планирует, ни счётчика, ни очереди нет. В 09:25 служба завершается. Таймер считает следующее срабатывание от 09:00 плюс интервал, получает 09:10, видит, что это прошлое, и запускает службу немедленно. В 09:50 она завершается — и всё повторяется. Формально расписание соблюдено. Фактически у вас непрерывный цикл без единой секунды простоя.
Симптом на глаз узнаётся мгновенно: в journalctl записи Started и Finished идут встык, разрыв между окончанием одного прогона и началом следующего — ноль-две секунды, круглые сутки. При этом systemctl list-timers выглядит абсолютно здоровым, никаких ошибок, никаких failed. Именно поэтому такое живёт на серверах месяцами: мониторинг молчит, а пользователи просто жалуются, что «всё тормозит».
# считаем реальные интервалы между стартами за сутки
journalctl -u lms-sync.service --since -24h -o short-unix \
| grep -F 'Starting ' | awk '{print $1}' \
| awk 'NR>1{printf "%.0f сек\n", $1-p} {p=$1}' | sort -n | uniq -c- Точка отсчёта для OnCalendar= — предыдущее срабатывание, а не завершение службы.
- Срабатывание, оказавшееся в прошлом, отрабатывает немедленно.
- Пока служба активна, таймер ничего не планирует и не копит — после завершения догон всегда один.
- Диагностика: нулевая пауза между Finished и следующим Starting в journalctl.
OnUnitInactiveSec= — интервал, который считается от финиша
Если вам нужна фраза «подожди пятнадцать минут после того, как всё закончилось», то в systemd она пишется ровно одним ключом. OnUnitInactiveSec= определяет таймер относительно момента, когда активируемый таймером юнит был в последний раз деактивирован. Не запущен — деактивирован. Это и есть честная пауза после окончания, без всяких надстроек и без оглядки на версию systemd: ключ существует с самых ранних версий и работает везде.
Рядом живёт похожий на вид OnUnitActiveSec=, и вот его путают постоянно. Он считает от момента последней активации службы. То есть ведёт себя ровно как календарный таймер: если прогон длиннее интервала, точка отсчёта уходит в прошлое и вы получаете тот же непрерывный цикл. Разница между двумя ключами — одно слово в названии и принципиально разное поведение под нагрузкой. Мой выбор по умолчанию для любого тяжёлого задания — Inactive.
Тонкость, на которой спотыкаются при первой настройке: монотонному таймеру нужен стартовый пинок. Пока служба ни разу не деактивировалась, отсчитывать не от чего, и таймер не сработает никогда. Поэтому OnUnitInactiveSec= всегда комбинируют с чем-то, что даёт первый запуск: обычно OnBootSec=, иногда OnActiveSec=. В мане это описано прямо: комбинируя OnBootSec= и OnUnitActiveSec=, можно определить таймер, который срабатывает через регулярные интервалы. С Inactive схема ровно такая же.
# /etc/systemd/system/lms-sync.timer
[Unit]
Description=Синхронизация с учебной платформой: пауза 10 минут ПОСЛЕ окончания
[Timer]
OnBootSec=3min
OnUnitInactiveSec=10min
AccuracySec=10s
Unit=lms-sync.service
[Install]
WantedBy=timers.targetЧего вы теряете, уйдя с календаря на монотонный таймер. Во-первых, Persistent= перестаёт действовать — он имеет эффект только на таймерах с OnCalendar=. Для задания, которое и так крутится каждые десять минут, это не потеря вообще. Во-вторых, привязка к стенным часам исчезает: прогоны будут плыть относительно круглых значений. Если бизнесу нужно строго «в 03:30 и ни минутой позже» — монотонный таймер не подходит, там нужен календарь. В-третьих, по формулировке мана монотонные часы при suspend, как правило, тоже встают на паузу (если не включён WakeSystem=), но для сервера в стойке это неактуально.
- OnUnitInactiveSec= — от последней деактивации службы. Настоящая пауза после окончания.
- OnUnitActiveSec= — от последней активации. Ведёт себя как календарь и даёт тот же непрерывный цикл.
- Монотонный таймер надо чем-то завести: OnBootSec= или OnActiveSec= в том же юните.
- Persistent= на монотонных таймерах не действует — только с OnCalendar=.
- Несколько выражений в одном [Timer] складываются: сработает то, что наступит раньше.
DeferReactivation=yes: правильный ответ, если у вас systemd 257 и новее
В systemd 257 в календарные таймеры добавили булев ключ DeferReactivation= — в NEWS это пункт раздела CHANGES WITH 257 про Calendar .timer units. Формулировка из мана: когда он включён, таймер планирует следующее срабатывание исходя из перехода запускаемого юнита в неактивное состояние, а не из времени последнего срабатывания. И дальше — прямым текстом про наш случай: это наиболее заметно, когда служба работает дольше, чем интервал таймера; с включённой настройкой таймер запланирует следующее срабатывание от момента завершения службы, и ему придётся дождаться следующего календарного времени срабатывания.
Ключевое отличие от OnUnitInactiveSec= — календарная сетка сохраняется. Таймер не отсчитывает интервал от финиша, а просто перестаёт считать пропущенные срабатывания долгом и ждёт ближайшего подходящего момента по календарю. При OnCalendar=*:0/10 и прогоне с 09:00 до 09:25 служба стартует не мгновенно в 09:25, а в 09:30. Для заданий, которые логически привязаны к круглым значениям времени, это заметно приятнее монотонного таймера.
Ограничений из мана два, и оба важные. Первое: настройка имеет эффект только если задан реальный календарный таймер через OnCalendar=. С монотонными выражениями она молча игнорируется. Второе: по умолчанию false, то есть на всех ваших существующих таймерах поведение осталось старым, включать надо руками. И третье, уже не из мана, а из практики: systemd на неизвестный ключ в юните не падает — он пишет в журнал предупреждение (в 252 оно выглядит как Unknown key name 'DeferReactivation' in section 'Timer', ignoring., в новых версиях — Unknown key 'DeferReactivation' in section [Timer], ignoring.) и грузит юнит дальше как ни в чём не бывало. То есть на Debian 12 вы честно допишете DeferReactivation=yes, systemctl daemon-reload отработает без ошибки, а поведение не изменится ни на грамм.
# сначала версия — без этого не начинаем
$ systemctl --version | head -1
systemd 257 (257.13-1~deb13u1)
# и обязательная проверка юнита после правки
$ systemd-analyze verify /etc/systemd/system/lms-sync.timer
# молчание = ключ распознан; строка Unknown key … DeferReactivation = ваша версия его не знаетЧто где стоит по состоянию на сентябрь 2026: Debian 12 bookworm — systemd 252.39, ключа нет; Ubuntu 24.04 LTS — 255.4, ключа нет; Debian 13 trixie — 257.13, ключ есть; Ubuntu 26.04 LTS — 259.5, ключ есть, а в разрабатываемом выпуске уже 261. Отдельно скажу, чего делать не надо: тащить systemd из backports или собирать его руками ради одного ключа. Оно того не стоит — OnUnitInactiveSec= решает ту же задачу на любой версии. Обновляйте дистрибутив по своему графику, а расписание чините тем, что есть под рукой. Бонусом в 258 появился RandomizedOffsetSec= — стабильный случайный сдвиг для OnCalendar=-таймеров, который, в отличие от RandomizedDelaySec=, сохраняет заданную периодичность событий даже при частых перезагрузках машины. Для парка машин, которые долбят один общий бэкап-сервер, штука полезная.
- DeferReactivation= добавлен в systemd 257, по умолчанию false.
- Работает только вместе с OnCalendar=, на монотонных таймерах игнорируется.
- Даёт ожидание ближайшего календарного момента вместо мгновенного повторного запуска.
- Неизвестный ключ не ломает юнит — только предупреждение Unknown key в журнале и полное отсутствие эффекта.
- Проверка: systemctl --version и systemd-analyze verify на файл таймера.
Разбор из практики: компьютерная школа «Терминал-школа», 31 рабочее место
Компьютерная школа «Терминал-школа»: 31 рабочее место — три учебных класса, преподавательская и администрация. Инфраструктура компактная: один сервер в собственной серверной с гипервизором, на нём файловый сервер с домашними папками учеников и отдельная Linux-виртуалка на Debian 12 (systemd 252.39, 4 vCPU, 8 ГБ) под обвязку — питоновский скрипт, который забирает с онлайн-платформы обучения списки групп, расписание и сданные домашние работы, раскладывает файлы по папкам учеников и отправляет обратно отметки о проверке. Пришли ко мне с формулировкой «сервер синхронизации постоянно загружен, в классах тормозит открытие файлов, наверное надо добавить ядер».
Что я увидел при разборе. Таймер с OnCalendar=*:0/10, служба Type=oneshot, скрипт исторически отрабатывал за три-четыре минуты. С началом учебного года число групп и сдаваемых работ выросло примерно втрое, и прогон растянулся до 25–40 минут. Load average на четырёх vCPU держался в диапазоне 8–11 круглосуточно, включая ночь и выходные, когда в классах никого нет. В journalctl за сутки — 43 прогона, идущих встык: разрыв между Finished и следующим Starting от нуля до двух секунд. Простоя у машины не было буквально ни минуты за трое суток.
Дальше — самое поучительное. До меня коллеги уже пытались это чинить и повесили на ExecStart обёртку с flock. Логика понятная: «наверное, там параллельные копии, надо заблокировать». Не помогло, и не могло помочь: параллельных копий не было изначально, systemd их и так не создаёт. flock ничего не заблокировал, потому что блокировать было нечего. Это самая частая диагностическая ошибка в этом сюжете, и на неё стабильно уходит день-два чужого времени.
Была и вторая, дорогая часть проблемы. API учебной платформы начал отдавать 429 Too Many Requests — непрерывная долбёжка без пауз вылезла за лимит запросов для учётной записи школы. Скрипт честно ретраил, ретраи удлиняли прогон, удлинившийся прогон гарантировал мгновенный перезапуск. Классическая положительная обратная связь: чем хуже, тем чаще, чем чаще, тем хуже. Добавление ядер, о котором просил клиент, эту петлю не разорвало бы вообще никак.
Лечение заняло полчаса. systemd 252, значит DeferReactivation= недоступен — перевёл таймер на монотонный. Плюс TimeoutStartSec= (для Type=oneshot он ограничивает весь прогон), чтобы зависший на сетевом таймауте прогон не держал юнит бесконечно, и уведомление на падение через OnFailure= — обратите внимание, этот ключ живёт в секции [Unit], а не [Service]. Вот что получилось:
# /etc/systemd/system/lms-sync.service
[Unit]
Description=Синхронизация с учебной платформой
Wants=network-online.target
After=network-online.target
OnFailure=notify-fail@%n.service
[Service]
Type=oneshot
User=sync
ExecStart=/opt/lms-sync/venv/bin/python /opt/lms-sync/run.py
TimeoutStartSec=2700
# /etc/systemd/system/lms-sync.timer
[Unit]
Description=Синхронизация с учебной платформой: пауза 10 минут после окончания
[Timer]
OnBootSec=3min
OnUnitInactiveSec=10min
AccuracySec=10s
[Install]
WantedBy=timers.targetИтог через две недели наблюдения. Прогонов в сутки стало 38–44 против прежних 43 — то есть полезной работы ровно столько же. Load average упал с 9,4 до 1,8 в среднем. Длительность одного прогона сократилась с 25–40 минут до 6–9: скрипт перестал конкурировать сам с собой за диск и, главное, перестал упираться в лимит API — ответы 429 исчезли полностью на третьи сутки. Жалобы преподавателей на медленное открытие файлов в классах прекратились, потому что виртуалка синхронизации больше не выедала общий дисковый массив непрерывными записями. Ядер не добавляли.
Отдельно проговорили с заказчиком то, о чём я писал выше: строгой привязки к круглым десяти минутам теперь нет, прогоны плывут. Ему это оказалось безразлично — важно было, чтобы сданная работа появлялась в папке ученика в течение получаса, а не красота расписания. Через полгода эту виртуалку переставили на Debian 13 и формально появилась возможность вернуться на OnCalendar= с DeferReactivation=yes. Не стали. Монотонный таймер работает, поведение всем понятно, трогать рабочее ради более модного ключа — плохая привычка.
- Симптом: LA 9,4 на 4 vCPU круглосуточно при 43 прогонах в сутки и нулевых паузах между ними.
- Ложный след: flock на ExecStart — лечит несуществующие дубли, не лечит отсутствие паузы.
- Усилитель проблемы: 429 от API учебной платформы, ретраи, удлинение прогона, мгновенный рестарт.
- Решение: OnBootSec=3min + OnUnitInactiveSec=10min, TimeoutStartSec=2700, OnFailure= в секции [Unit].
- Результат: LA 9,4 → 1,8, длительность прогона 25–40 мин → 6–9 мин, число полезных прогонов не изменилось.
Если DeferReactivation недоступен: что реально работает
Вариантов на самом деле немного, и я расставлю их по приоритету так, как расставляю у клиентов. Первый и основной — перевод на OnUnitInactiveSec=. Работает на любой версии systemd, поведение читается из юнита за пять секунд, ничего лишнего в системе не появляется. Если у задания нет жёсткой привязки к часам, дальше можно не читать, берите этот вариант.
Второй — просто увеличить интервал OnCalendar= с запасом относительно худшего наблюдаемого времени прогона. Способ туповатый, но у него есть законная ниша: когда разброс длительности небольшой и предсказуемый, а календарная сетка важна. Проблема в том, что запас имеет свойство съедаться ростом объёмов, и через полгода вы вернётесь к тому же самому. Обязательно вешайте на такой вариант мониторинг длительности.
Третий — разделить таймер и решение о запуске. Таймер дёргает лёгкий диспетчер каждую минуту, а тот сам смотрит на маркер последнего успешного завершения и решает, пора или нет. Гибко, позволяет описать любую логику вроде «не чаще раза в десять минут, но не в момент ночного бэкапа», и заодно даёт естественную точку для метрик. Плата — своя логика, которую надо сопровождать. Я иду на это, только когда правил действительно несколько.
А вот чего делать не стоит. Не надо ставить sleep в конец скрипта: юнит останется активным всё время сна, таймер всё это время просто ждёт, а systemctl status начнёт врать про длительность работы; к тому же при OnCalendar= пауза от sleep не спасёт от немедленного догона, если прогон вместе со сном длиннее интервала. Не надо навешивать flock, если единственная задача — пауза: параллельных копий и так нет, а лишняя обёртка усложняет диагностику. Не надо использовать Restart= на Type=oneshot службе как способ зациклить работу — Restart=always для oneshot вообще не допускается, а on-failure нужен для повторов после сбоя, а не для расписания. И не надо ставить StartLimitIntervalSec с расчётом «пусть systemd сам заглушит частые запуски»: он заглушит, но через failed-состояние юнита, и вы получите остановленное расписание вместо аккуратной паузы.
Отдельно про мониторинг, потому что описанная поломка мониторингом обычно не ловится. Ошибок нет, юниты зелёные, list-timers выглядит здорово. Ловить надо две метрики: длительность последнего прогона и время, прошедшее с последнего успешного завершения. Обе снимаются одной командой без единой сторонней утилиты:
# длительность последнего прогона и время с момента завершения
systemctl show lms-sync.service \
-p ExecMainStartTimestampMonotonic \
-p ExecMainExitTimestampMonotonic \
-p Result -p NRestarts
# сколько прогонов было за сутки — если равно расписанию, всё в порядке
journalctl -u lms-sync.service --since -24h | grep -c 'Starting '- Приоритет 1: OnUnitInactiveSec= — работает везде, читается сразу, ничего не требует.
- Приоритет 2: DeferReactivation=yes на systemd 257+, если нужна календарная сетка.
- Приоритет 3: увеличенный интервал OnCalendar= с мониторингом длительности.
- Приоритет 4: собственный диспетчер — только когда правил запуска действительно несколько.
- Никогда: sleep в хвосте скрипта, flock вместо паузы, StartLimit как регулятор частоты.
Чек-лист: с чего начать и на что можно забить
Порядок действий, если вы прямо сейчас смотрите на подозрительно занятый сервер. Сначала посчитайте паузы между прогонами за сутки — это одна команда из раздела выше, и она сразу разделяет две гипотезы. Если паузы нулевые, у вас описанный здесь сюжет, и лечится он правкой одного файла. Если паузы нормальные, а нагрузка всё равно высокая — проблема в самом задании, и таймер тут ни при чём, идите профилировать скрипт.
Дальше посмотрите systemctl --version и решите, доступен ли вам DeferReactivation=. Затем ответьте на единственный содержательный вопрос: нужна ли этому заданию привязка к стенным часам. Нужна и версия свежая — DeferReactivation=yes. Нужна, а версия старая — увеличенный интервал плюс мониторинг. Не нужна — OnUnitInactiveSec= и не думайте об этом больше. Заодно проверьте, нет ли где RemainAfterExit=yes на службе под повторяющимся таймером: это противоположная поломка, задание молча не выполняется вообще.
На что можно спокойно забить. На страх параллельных копий — их нет, гарантия записана в мане, lock-файлы под эту задачу не нужны. На точность до секунды: AccuracySec= по умолчанию минута, и для обменов, выгрузок и бэкапов этого более чем достаточно; выкручивать в 1us имеет смысл, только если вы точно знаете зачем, и заранее готовы к росту числа пробуждений процессора. На дискуссию cron против systemd timer: в 2026 году спорить не о чем, но и переписывать работающие crontab-строчки просто из принципа — пустая трата вечера.
И последнее, из области здравого смысла. Все описанные ключи — про то, когда задание запускается. Ни один из них не отвечает на вопрос, что делать с прогоном, который завис на сетевом таймауте и висит третий час. Для этого есть TimeoutStartSec= на службе, и я ставлю его всегда, даже когда уверен, что задание быстрое. Уверенность имеет свойство заканчиваться ровно в тот момент, когда у внешнего API отваливается плечо и ваш скрипт садится ждать ответа до конца времён.
- Посчитать паузы между прогонами за сутки — первое действие, одна команда.
- Проверить systemctl --version перед любой правкой с DeferReactivation=.
- Решить: нужна календарная сетка или нужна пауза. Это разные ключи.
- Проверить службы под таймерами на RemainAfterExit=yes.
- Всегда ставить TimeoutStartSec= — независимо от выбранной схемы расписания.
Частые вопросы
Запустится ли вторая копия службы, если таймер сработал во время её работы?
Нет. В systemd.timer(5) сказано прямо: если активируемый юнит уже активен в момент срабатывания таймера, он не перезапускается, а просто остаётся работать, и концепции порождения новых экземпляров службы в этом случае не существует. Дубли процессов возможны только если сама служба врёт о своём состоянии — неправильный Type=forking, PIDFile или запуск работы в фон внутри скрипта.
Почему следующий запуск происходит сразу же после окончания предыдущего?
Потому что календарный таймер после завершения службы пересчитывает следующее срабатывание от предыдущего срабатывания, а не от момента завершения. Если прогон длился дольше интервала, вычисленное время оказывается в прошлом, и systemd отрабатывает его немедленно. Получается непрерывный цикл без пауз — при этом никаких ошибок в журнале нет и мониторинг молчит.
Чем OnUnitActiveSec= отличается от OnUnitInactiveSec=?
Точкой отсчёта. OnUnitActiveSec= считает от момента последней активации службы и при долгих прогонах даёт тот же мгновенный перезапуск, что и календарь. OnUnitInactiveSec= считает от момента последней деактивации, то есть даёт настоящую паузу после окончания работы. Для тяжёлых заданий нужен именно второй.
Работает ли DeferReactivation= вместе с OnUnitInactiveSec=?
Нет, и не нужно. В мане указано, что настройка имеет эффект только если задан календарный таймер через OnCalendar=. С монотонными выражениями она молча игнорируется. Да и смысла нет: OnUnitInactiveSec= уже отсчитывает интервал от завершения службы.
Что будет, если написать DeferReactivation=yes в systemd 252 или 255?
Ничего хорошего и ничего заметного. systemd запишет в журнал предупреждение о неизвестном ключе в секции [Timer] и проигнорирует строку, а юнит загрузится как валидный. Ошибки при daemon-reload не будет, поведение не изменится. Проверять надо через systemctl --version и systemd-analyze verify на файл таймера.
Поможет ли flock или lock-файл в скрипте?
От параллельных копий — да, но их и так нет, systemd их не создаёт. От непрерывного перезапуска встык — не поможет никак: блокировка снимается в момент завершения процесса, и следующий прогон стартует сразу же. Это самая частая ложная гипотеза при разборе такой ситуации.
Можно ли одновременно держать строгое расписание по часам и паузу после окончания?
Нет, если прогон бывает длиннее интервала — эти требования противоречат друг другу по определению. Ближайший компромисс — DeferReactivation=yes на systemd 257+: календарная сетка сохраняется, но после долгого прогона следующее время считается от момента завершения службы, поэтому мгновенного перезапуска нет — таймер ждёт ближайшего календарного момента.
Источники
- systemd.timer(5), актуальная редакция — Разделы OnUnitActiveSec=/OnUnitInactiveSec=, OnCalendar=, DeferReactivation= (Added in version 257), Persistent=, AccuracySec=, RandomizedDelaySec=, RandomizedOffsetSec= (v258), а также абзац про поведение при уже активном юните. https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html
- systemd.timer(5) на man7.org — Зеркало страницы руководства с теми же формулировками, удобно для быстрой сверки цитат. https://man7.org/linux/man-pages/man5/systemd.timer.5.html
- systemd NEWS, CHANGES WITH 257 — Раздел CHANGES WITH 257: «Calendar .timer units gained a new boolean DeferReactivation= option…» — при повторном срабатывании во время работы службы немедленная реактивация после её завершения пропускается; раздел CHANGES WITH 258 — новый RandomizedOffsetSec=. https://github.com/systemd/systemd/blob/main/NEWS
- Исходник страницы руководства man/systemd.timer.xml — Первоисточник формулировок про DeferReactivation= (v257), RandomizedOffsetSec= (v258) и про уже активный юнит; логика планирования — src/core/timer.c (timer_enter_waiting, timer_trigger_notify). https://github.com/systemd/systemd/blob/main/man/systemd.timer.xml
- Debian package: systemd (trixie) — Версия systemd в Debian 13 — 257.13-1~deb13u1, то есть DeferReactivation= доступен из коробки. https://packages.debian.org/trixie/systemd
- Debian package: systemd (bookworm) — Версия systemd в Debian 12 — 252.39-1~deb12u2, DeferReactivation= не поддерживается. https://packages.debian.org/bookworm/systemd
- Launchpad: systemd package in Ubuntu — Версии по выпускам: 24.04 LTS — 255.4-1ubuntu8.x, 26.04 LTS — 259.5. https://launchpad.net/ubuntu/+source/systemd
- Use systemd timers instead of cronjobs, opensource.com — Обзорная статья по таймерам systemd: структура .timer/.service, монотонные и календарные выражения, list-timers. https://opensource.com/article/20/7/systemd-timers
