Сервер с 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?
Комментарии 0