Fix: Graceful Telegram bot shutdown to prevent 409 Conflict

Introduces a `botCancel` context and a global `StopBot()` function to ensure the Telegram bot's Long Polling operation is safely terminated (via context cancellation) before the service restarts. This prevents the "Conflict: another update consumer is running" (409) error upon panel restart.

Changes:
- Added `botCancel context.CancelFunc` to manage context cancellation.
- Implemented global `StopBot()` function.
- Updated `Tgbot.Stop()` to call `StopBot()`.
- Modified `Tgbot.OnReceive()` to use the new cancellable context for `UpdatesViaLongPolling`.
This commit is contained in:
OleksandrParshyn 2025-10-31 13:29:20 +01:00 committed by GitHub
parent 713a7328f6
commit 9ac8fdf3d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -39,6 +39,10 @@ import (
var (
bot *telego.Bot
// botCancel stores the function to cancel the context, stopping Long Polling gracefully.
botCancel context.CancelFunc
botHandler *th.BotHandler
adminIds []int64
isRunning bool
@ -306,8 +310,13 @@ func (t *Tgbot) SetHostname() {
hostname = host
}
// Stop stops the Telegram bot and cleans up resources.
// Stop safely stops the Telegram bot's Long Polling operation.
// This method now calls the global StopBot function and cleans up other resources.
func (t *Tgbot) Stop() {
// Call the global StopBot function to gracefully shut down Long Polling
StopBot()
// Stop the bot handler (in case the goroutine hasn't exited yet)
if botHandler != nil {
botHandler.Stop()
}
@ -316,6 +325,24 @@ func (t *Tgbot) Stop() {
adminIds = nil
}
// StopBot safely stops the Telegram bot's Long Polling operation by cancelling its context.
// This is the global function called from main.go's signal handler and t.Stop().
func StopBot() {
if botCancel != nil {
logger.Info("Sending cancellation signal to Telegram bot...")
// Calling botCancel() cancels the context passed to UpdatesViaLongPolling,
// which stops the Long Polling operation and closes the updates channel,
// allowing the th.Start() goroutine to exit cleanly.
botCancel()
botCancel = nil
// Giving the goroutine a small delay to exit cleanly.
time.Sleep(1 * time.Second)
logger.Info("Telegram bot successfully stopped.")
}
}
// encodeQuery encodes the query string if it's longer than 64 characters.
func (t *Tgbot) encodeQuery(query string) string {
// NOTE: we only need to hash for more than 64 chars
@ -346,7 +373,16 @@ func (t *Tgbot) OnReceive() {
Timeout: 30, // Increased timeout to reduce API calls
}
updates, _ := bot.UpdatesViaLongPolling(context.Background(), &params)
// updates, _ := bot.UpdatesViaLongPolling(context.Background(), &params)
// --- GRACEFUL SHUTDOWN FIX: Context creation ---
// Create a context with cancellation and store the cancel function.
var ctx context.Context
ctx, botCancel = context.WithCancel(context.Background())
// --------------------------------------------------
// Get updates channel using the created context. This channel will close when ctx is cancelled.
updates, _ := bot.UpdatesViaLongPolling(ctx, &params) // <<<
botHandler, _ = th.NewBotHandler(bot, updates)