Сервер с Telegram-ботом выключился в 12:00 и поднялся в 12:17. По расписанию бот должен был проверить подписки в 12:05, 12:10 и 12:15. Если после старта он ищет подписки, истёкшие за последние пять минут, запись со сроком 12:03 останется за бортом. К следующему запуску она станет ещё старше и в выборку так и не попадёт.

У календарного таймера systemd есть Persistent=true. При активации таймера systemd запустит сервис, если за время простоя пропущено хотя бы одно срабатывание. В нашем примере будет один догоняющий запуск. Разобрать всё накопившееся за три пропущенных интервала должен сам обработчик. Документация systemd.timer

Пара юнитов для обработчика

access-expiry.timer

``ini [Timer] OnCalendar=--* :00/5:00 UTC Persistent=true

[Install] WantedBy=timers.target `

access-expiry.service

ini [Service] Type=oneshot User=access-bot TimeoutStartSec=90s ExecStart=/usr/bin/python3 /opt/access-bot/revoke_expired.py

Если обработчик уже развёрнут под системным пользователем access-bot, положите оба юнита в /etc/systemd/system, выполните sudo systemctl daemon-reload и включите таймер командой sudo systemctl enable --now access-expiry.timer. Расписание можно проверить через systemd-analyze calendar ‘-- *:00/5:00 UTC’. Лимит в 90 секунд здесь для примера. Если процесс зависнет на запросе к Telegram, следующие срабатывания таймера не запустят параллельные копии сервиса. Поэтому обработчику нужен собственный сетевой тайм-аут, а юниту — предел времени на весь прогон. Документация systemd.service

Что проверить после простоя

Допустим, к 12:17 у бота есть три записи.

Что произошло

Действие обработчика

Отозвать доступ, хотя с момента истечения прошло 14 минут

Пропустить участника, у него снова есть действующий доступ

Оставить действие незавершённым, чтобы следующий прогон его подобрал

На практике выборку строят по текущему праву доступа участника к каналу. Результат лучше сохранять после обработки каждого участника. Тогда при остановке процесса посередине следующий прогон подхватит то, что осталось. Если Telegram успел удалить участника, но ответ потерялся, бот с правами администратора может сверить его статус через getChatMember. Документация Telegram Bot API

Есть ещё гонка между оплатой и отзывом. Повторная проверка срока перед запросом уменьшает окно, но оплата всё равно может прийти сразу после проверки. Если продление и отзыв обрабатывают разные воркеры, изменения доступа одного участника нужно выполнять последовательно. Если отзыв успел сработать, обработчик продления должен восстановить возможность входа. Например, снять блокировку через unbanChatMember с only_if_banned=true` и выдать приглашение. Пользователь входит по ссылке сам.

На стенде я бы дал таймеру сработать один раз, затем остановил его, пропустил несколько отметок расписания и включил между ними. В журнале должны быть видны один догоняющий запуск и три разных решения из таблицы. На следующем прогоне стоит проверить, что бот вернулся к записи со временной ошибкой.

Суть в том, что после перезапуска бот должен разобраться с накопившейся работой. Таймер запускает обработчик, а тот проверяет актуальный доступ каждого участника. У одного подписка истекла, второй уже продлил её, у третьего отзыв доступа нужно повторить после ошибки.

Поделитесь, пожалуйста, как у вас бот обрабатывает продление, если запрос на отзыв доступа уже отправлен в Telegram?