Feat: Add IMAP connection pool status logging and disable bb8 idle timeout

This commit is contained in:
rustmailer
2025-12-08 03:41:56 +08:00
parent 57df3466e7
commit 20970b4fb6
5 changed files with 51 additions and 29 deletions
+2 -2
View File
@@ -25,7 +25,7 @@ use crate::modules::error::code::ErrorCode;
use super::create_api_error_response; 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; pub struct Timeout;
@@ -63,7 +63,7 @@ impl<E: Endpoint> Endpoint for TimeoutEndpoint<E> {
error!("Request timed out after {} seconds", seconds); error!("Request timed out after {} seconds", seconds);
Err(create_api_error_response( Err(create_api_error_response(
&format!( &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 seconds
), ),
ErrorCode::RequestTimeout, ErrorCode::RequestTimeout,
+1 -1
View File
@@ -63,7 +63,7 @@ impl EmailClientExecutors {
} }
let pool = build_imap_pool(account_id).await?; 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) { match self.imap.try_entry(account_id) {
Some(dashmap::mapref::entry::Entry::Occupied(entry)) => Ok(entry.get().clone()), Some(dashmap::mapref::entry::Entry::Occupied(entry)) => Ok(entry.get().clone()),
+1 -16
View File
@@ -16,15 +16,11 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
use std::{fmt::Formatter, u32};
use crate::raise_error;
use bb8::RunError;
use code::ErrorCode; use code::ErrorCode;
use poem::http::StatusCode; use poem::http::StatusCode;
use poem_openapi::{payload::Json, ApiResponse, Object}; use poem_openapi::{payload::Json, ApiResponse, Object};
use snafu::{Location, Snafu}; use snafu::{Location, Snafu};
use std::{fmt::Formatter, u32};
pub mod code; pub mod code;
pub mod handler; pub mod handler;
@@ -43,17 +39,6 @@ pub enum BichonError {
pub type BichonResult<T, E = BichonError> = std::result::Result<T, E>; pub type BichonResult<T, E = BichonError> = std::result::Result<T, E>;
impl From<RunError<BichonError>> for BichonError {
fn from(e: RunError<BichonError>) -> 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)] #[derive(Debug, Clone, Object)]
pub struct ApiError { pub struct ApiError {
pub message: String, pub message: String,
+46 -9
View File
@@ -16,7 +16,6 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
use crate::modules::account::state::AccountRunningState; use crate::modules::account::state::AccountRunningState;
use crate::modules::cache::imap::mailbox::MailBox; use crate::modules::cache::imap::mailbox::MailBox;
use crate::modules::cache::imap::sync::flow::{generate_uid_sequence_hashset, BATCH_SIZE}; 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::modules::{error::BichonResult, imap::manager::ImapConnectionManager};
use crate::raise_error; use crate::raise_error;
use async_imap::types::{Mailbox, Name}; use async_imap::types::{Mailbox, Name};
use bb8::Pool; use bb8::{Pool, RunError};
use futures::TryStreamExt; use futures::TryStreamExt;
use std::collections::HashSet; use std::collections::HashSet;
use tantivy::doc; use tantivy::doc;
@@ -36,16 +35,17 @@ use tracing::info;
const BODY_FETCH_COMMAND: &str = "(UID INTERNALDATE RFC822.SIZE BODY.PEEK[])"; const BODY_FETCH_COMMAND: &str = "(UID INTERNALDATE RFC822.SIZE BODY.PEEK[])";
pub struct ImapExecutor { pub struct ImapExecutor {
account_id: u64,
pool: Pool<ImapConnectionManager>, pool: Pool<ImapConnectionManager>,
} }
impl ImapExecutor { impl ImapExecutor {
pub fn new(pool: Pool<ImapConnectionManager>) -> Self { pub fn new(account_id: u64, pool: Pool<ImapConnectionManager>) -> Self {
Self { pool } Self { account_id, pool }
} }
pub async fn list_all_mailboxes(&self) -> BichonResult<Vec<Name>> { pub async fn list_all_mailboxes(&self) -> BichonResult<Vec<Name>> {
let mut session = self.pool.get().await?; let mut session = self.get_connection().await?;
let list = session let list = session
.list(Some(""), Some("*")) .list(Some(""), Some("*"))
.await .await
@@ -58,7 +58,7 @@ impl ImapExecutor {
} }
pub async fn examine_mailbox(&self, mailbox_name: &str) -> BichonResult<Mailbox> { pub async fn examine_mailbox(&self, mailbox_name: &str) -> BichonResult<Mailbox> {
let mut session = self.pool.get().await?; let mut session = self.get_connection().await?;
session session
.examine(mailbox_name) .examine(mailbox_name)
.await .await
@@ -66,7 +66,7 @@ impl ImapExecutor {
} }
pub async fn uid_search(&self, mailbox_name: &str, query: &str) -> BichonResult<HashSet<u32>> { pub async fn uid_search(&self, mailbox_name: &str, query: &str) -> BichonResult<HashSet<u32>> {
let mut session = self.pool.get().await?; let mut session = self.get_connection().await?;
session session
.examine(mailbox_name) .examine(mailbox_name)
.await .await
@@ -142,7 +142,7 @@ impl ImapExecutor {
assert!(page > 0, "Page number must be greater than 0"); assert!(page > 0, "Page number must be greater than 0");
assert!(page_size > 0, "Page size 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 let total = session
.examine(encoded_mailbox_name) .examine(encoded_mailbox_name)
.await .await
@@ -211,7 +211,7 @@ impl ImapExecutor {
uid_set: &str, uid_set: &str,
encoded_mailbox_name: &str, encoded_mailbox_name: &str,
) -> BichonResult<()> { ) -> BichonResult<()> {
let mut session = self.pool.get().await?; let mut session = self.get_connection().await?;
session session
.examine(encoded_mailbox_name) .examine(encoded_mailbox_name)
.await .await
@@ -238,4 +238,41 @@ impl ImapExecutor {
} }
Ok(()) Ok(())
} }
async fn get_connection(
&self,
) -> BichonResult<bb8::PooledConnection<'_, ImapConnectionManager>> {
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
));
}
},
}
}
} }
+1 -1
View File
@@ -49,7 +49,7 @@ pub async fn build_imap_pool(account_id: u64) -> BichonResult<Pool<ImapConnectio
let manager = ImapConnectionManager::new(account_id); let manager = ImapConnectionManager::new(account_id);
let pool = Pool::builder() let pool = Pool::builder()
.connection_timeout(Duration::from_secs(30)) .connection_timeout(Duration::from_secs(30))
.idle_timeout(Duration::from_secs(120)) //.idle_timeout(Duration::from_secs(120))
.retry_connection(true) .retry_connection(true)
.max_size(10) .max_size(10)
.test_on_check_out(true) .test_on_check_out(true)