From 20970b4fb6325f5135e01e23ac2913e68a2241cb Mon Sep 17 00:00:00 2001 From: rustmailer Date: Mon, 8 Dec 2025 03:41:56 +0800 Subject: [PATCH] Feat: Add IMAP connection pool status logging and disable bb8 idle timeout --- src/modules/common/timeout.rs | 4 +-- src/modules/context/executors.rs | 2 +- src/modules/error/mod.rs | 17 +--------- src/modules/imap/executor.rs | 55 ++++++++++++++++++++++++++------ src/modules/imap/pool.rs | 2 +- 5 files changed, 51 insertions(+), 29 deletions(-) diff --git a/src/modules/common/timeout.rs b/src/modules/common/timeout.rs index 98f1270..ce63826 100644 --- a/src/modules/common/timeout.rs +++ b/src/modules/common/timeout.rs @@ -25,7 +25,7 @@ use crate::modules::error::code::ErrorCode; use super::create_api_error_response; -pub const TIMEOUT_HEADER: &str = "X-RustMailer-Timeout-Seconds"; +pub const TIMEOUT_HEADER: &str = "X-Bichon-Timeout-Seconds"; pub struct Timeout; @@ -63,7 +63,7 @@ impl Endpoint for TimeoutEndpoint { error!("Request timed out after {} seconds", seconds); Err(create_api_error_response( &format!( - "Request timed out after {} seconds (timeout set via X-RustMailer-Timeout-Seconds header, max allowed: 600 seconds)", + "Request timed out after {} seconds (timeout set via X-Bichon-Timeout-Seconds header, max allowed: 600 seconds)", seconds ), ErrorCode::RequestTimeout, diff --git a/src/modules/context/executors.rs b/src/modules/context/executors.rs index c2ccb8f..baa0e38 100644 --- a/src/modules/context/executors.rs +++ b/src/modules/context/executors.rs @@ -63,7 +63,7 @@ impl EmailClientExecutors { } let pool = build_imap_pool(account_id).await?; - let new_executor = Arc::new(ImapExecutor::new(pool)); + let new_executor = Arc::new(ImapExecutor::new(account_id, pool)); match self.imap.try_entry(account_id) { Some(dashmap::mapref::entry::Entry::Occupied(entry)) => Ok(entry.get().clone()), diff --git a/src/modules/error/mod.rs b/src/modules/error/mod.rs index 0c0735a..4afd2e5 100644 --- a/src/modules/error/mod.rs +++ b/src/modules/error/mod.rs @@ -16,15 +16,11 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - -use std::{fmt::Formatter, u32}; - -use crate::raise_error; -use bb8::RunError; use code::ErrorCode; use poem::http::StatusCode; use poem_openapi::{payload::Json, ApiResponse, Object}; use snafu::{Location, Snafu}; +use std::{fmt::Formatter, u32}; pub mod code; pub mod handler; @@ -43,17 +39,6 @@ pub enum BichonError { pub type BichonResult = std::result::Result; -impl From> for BichonError { - fn from(e: RunError) -> Self { - match e { - RunError::User(e) => e, - RunError::TimedOut => raise_error!( - "Timed out while attempting to acquire a connection from the pool".into(), - ErrorCode::ConnectionPoolTimeout - ), - } - } -} #[derive(Debug, Clone, Object)] pub struct ApiError { pub message: String, diff --git a/src/modules/imap/executor.rs b/src/modules/imap/executor.rs index de74de7..13f1320 100644 --- a/src/modules/imap/executor.rs +++ b/src/modules/imap/executor.rs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . - use crate::modules::account::state::AccountRunningState; use crate::modules::cache::imap::mailbox::MailBox; use crate::modules::cache::imap::sync::flow::{generate_uid_sequence_hashset, BATCH_SIZE}; @@ -27,7 +26,7 @@ use crate::modules::indexer::schema::SchemaTools; use crate::modules::{error::BichonResult, imap::manager::ImapConnectionManager}; use crate::raise_error; use async_imap::types::{Mailbox, Name}; -use bb8::Pool; +use bb8::{Pool, RunError}; use futures::TryStreamExt; use std::collections::HashSet; use tantivy::doc; @@ -36,16 +35,17 @@ use tracing::info; const BODY_FETCH_COMMAND: &str = "(UID INTERNALDATE RFC822.SIZE BODY.PEEK[])"; pub struct ImapExecutor { + account_id: u64, pool: Pool, } impl ImapExecutor { - pub fn new(pool: Pool) -> Self { - Self { pool } + pub fn new(account_id: u64, pool: Pool) -> Self { + Self { account_id, pool } } pub async fn list_all_mailboxes(&self) -> BichonResult> { - let mut session = self.pool.get().await?; + let mut session = self.get_connection().await?; let list = session .list(Some(""), Some("*")) .await @@ -58,7 +58,7 @@ impl ImapExecutor { } pub async fn examine_mailbox(&self, mailbox_name: &str) -> BichonResult { - let mut session = self.pool.get().await?; + let mut session = self.get_connection().await?; session .examine(mailbox_name) .await @@ -66,7 +66,7 @@ impl ImapExecutor { } pub async fn uid_search(&self, mailbox_name: &str, query: &str) -> BichonResult> { - let mut session = self.pool.get().await?; + let mut session = self.get_connection().await?; session .examine(mailbox_name) .await @@ -142,7 +142,7 @@ impl ImapExecutor { assert!(page > 0, "Page number must be greater than 0"); assert!(page_size > 0, "Page size must be greater than 0"); - let mut session = self.pool.get().await?; + let mut session = self.get_connection().await?; let total = session .examine(encoded_mailbox_name) .await @@ -211,7 +211,7 @@ impl ImapExecutor { uid_set: &str, encoded_mailbox_name: &str, ) -> BichonResult<()> { - let mut session = self.pool.get().await?; + let mut session = self.get_connection().await?; session .examine(encoded_mailbox_name) .await @@ -238,4 +238,41 @@ impl ImapExecutor { } Ok(()) } + + async fn get_connection( + &self, + ) -> BichonResult> { + match self.pool.get().await { + Ok(connection) => Ok(connection), + Err(e) => match e { + RunError::User(e) => Err(e), + RunError::TimedOut => { + let state = self.pool.state(); + tracing::warn!( + "{}: connections={}, idle={}, \ + get_started={}, get_direct={}, get_waited={}, get_timed_out={}, \ + wait_time_ms={}, created={}, closed_broken={}, closed_invalid={}, \ + closed_lifetime={}, closed_idle={}", + self.account_id, + state.connections, + state.idle_connections, + state.statistics.get_started, + state.statistics.get_direct, + state.statistics.get_waited, + state.statistics.get_timed_out, + state.statistics.get_wait_time.as_millis(), + state.statistics.connections_created, + state.statistics.connections_closed_broken, + state.statistics.connections_closed_invalid, + state.statistics.connections_closed_max_lifetime, + state.statistics.connections_closed_idle_timeout, + ); + return Err(raise_error!( + "Timed out while attempting to acquire a connection from the pool".into(), + ErrorCode::ConnectionPoolTimeout + )); + } + }, + } + } } diff --git a/src/modules/imap/pool.rs b/src/modules/imap/pool.rs index 35f10c3..f444b4a 100644 --- a/src/modules/imap/pool.rs +++ b/src/modules/imap/pool.rs @@ -49,7 +49,7 @@ pub async fn build_imap_pool(account_id: u64) -> BichonResult