mirror of
https://github.com/TheFunny/TelegramTwitterMediaBot.git
synced 2026-09-23 23:32:05 +00:00
fix: handle SIGTERM for graceful shutdown on docker stop
This commit is contained in:
@@ -65,7 +65,7 @@ Docker: `docker build -t tgxmb .` then `docker run --rm -d --name tgxmb --env-fi
|
|||||||
|
|
||||||
| File | Why it matters |
|
| File | Why it matters |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `crates/xmedia-bot/src/main.rs` | Startup sequence, webhook vs polling, graceful shutdown (ctrlc → sweep stop → admin msg → queue stop) |
|
| `crates/xmedia-bot/src/main.rs` | Startup sequence, webhook vs polling, graceful shutdown (SIGINT via teloxide ctrlc / SIGTERM via `stop_token` for docker, → sweep stop → admin msg → queue stop) |
|
||||||
| `crates/xmedia-bot/src/handlers.rs` | `CHAT_STORE`/`TASK_QUEUE`/`CONFIG` singletons (open `data/task_queue.db` **relative to CWD**); command dispatch; URL extraction; retry enqueue |
|
| `crates/xmedia-bot/src/handlers.rs` | `CHAT_STORE`/`TASK_QUEUE`/`CONFIG` singletons (open `data/task_queue.db` **relative to CWD**); command dispatch; URL extraction; retry enqueue |
|
||||||
| `crates/xmedia-bot/src/send.rs` | Constants `MAX_MEDIA_GROUP = 9`, `MAX_UPLOAD_BYTES = 10 MiB`; fallback chain; `classify_request_error` |
|
| `crates/xmedia-bot/src/send.rs` | Constants `MAX_MEDIA_GROUP = 9`, `MAX_UPLOAD_BYTES = 10 MiB`; fallback chain; `classify_request_error` |
|
||||||
| `crates/x-media/src/site/mod.rs` | Dispatcher, `Fetched`/`FetchError`, shared `CLIENT`, `download_media` (adds `Referer: https://www.pixiv.net/` for `pximg.net` hotlink protection) |
|
| `crates/x-media/src/site/mod.rs` | Dispatcher, `Fetched`/`FetchError`, shared `CLIENT`, `download_media` (adds `Referer: https://www.pixiv.net/` for `pximg.net` hotlink protection) |
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use teloxide::dptree::endpoint;
|
use teloxide::dptree::endpoint;
|
||||||
|
use teloxide::stop::StopToken;
|
||||||
use teloxide::types::{ChatId, InputFile, MessageId};
|
use teloxide::types::{ChatId, InputFile, MessageId};
|
||||||
use teloxide::update_listeners::webhooks;
|
use teloxide::update_listeners::{self, webhooks, UpdateListener};
|
||||||
use teloxide::prelude::*;
|
use teloxide::prelude::*;
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
use x_media::site;
|
use x_media::site;
|
||||||
@@ -14,6 +15,24 @@ mod state;
|
|||||||
|
|
||||||
use handlers::{CHAT_STORE, CONFIG, TASK_QUEUE};
|
use handlers::{CHAT_STORE, CONFIG, TASK_QUEUE};
|
||||||
|
|
||||||
|
/// Docker `stop` / `compose down` delivers SIGTERM, which teloxide's ctrlc
|
||||||
|
/// handler (SIGINT only) never sees — without this the process would die
|
||||||
|
/// before the graceful shutdown below (admin notice, queue drain). Stopping
|
||||||
|
/// the token unwinds the dispatcher exactly like Ctrl+C does.
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn spawn_sigterm_handler(stop_token: StopToken) {
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
|
||||||
|
.expect("failed to install SIGTERM handler");
|
||||||
|
sigterm.recv().await;
|
||||||
|
log::info!("SIGTERM received, stopping the dispatcher");
|
||||||
|
stop_token.stop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
fn spawn_sigterm_handler(_stop_token: StopToken) {}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
@@ -108,20 +127,36 @@ async fn main() {
|
|||||||
options = options.secret_token(secret.clone());
|
options = options.secret_token(secret.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut listener = webhooks::axum(bot.clone(), options)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create webhook listener");
|
||||||
|
let stop_token = listener.stop_token();
|
||||||
|
spawn_sigterm_handler(stop_token);
|
||||||
|
|
||||||
dispatcher
|
dispatcher
|
||||||
.dispatch_with_listener(
|
.dispatch_with_listener(
|
||||||
webhooks::axum(bot.clone(), options)
|
listener,
|
||||||
.await
|
|
||||||
.expect("Failed to create webhook listener"),
|
|
||||||
LoggingErrorHandler::with_custom_text("Error from update listener"),
|
LoggingErrorHandler::with_custom_text("Error from update listener"),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
} else {
|
} else {
|
||||||
log::info!("running in polling mode");
|
log::info!("running in polling mode");
|
||||||
dispatcher.dispatch().await;
|
// Same listener `dispatch()` builds internally — using
|
||||||
|
// `dispatch_with_listener` just exposes its stop token so SIGTERM can
|
||||||
|
// unwind the dispatcher before the graceful shutdown below.
|
||||||
|
let mut listener = update_listeners::polling_default(bot.clone()).await;
|
||||||
|
let stop_token = listener.stop_token();
|
||||||
|
spawn_sigterm_handler(stop_token);
|
||||||
|
|
||||||
|
dispatcher
|
||||||
|
.dispatch_with_listener(
|
||||||
|
listener,
|
||||||
|
LoggingErrorHandler::with_custom_text("Error from update listener"),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Graceful stop (Ctrl+C): stop the sweep, notify the admin, drain the queue.
|
// Graceful stop (Ctrl+C / SIGTERM): stop the sweep, notify the admin, drain the queue.
|
||||||
log::info!("Stopping bot");
|
log::info!("Stopping bot");
|
||||||
let _ = stop_tx.send(true);
|
let _ = stop_tx.send(true);
|
||||||
if let Some(admin) = CONFIG.admin_ids.first() {
|
if let Some(admin) = CONFIG.admin_ids.first() {
|
||||||
|
|||||||
Reference in New Issue
Block a user