Skip to content

pool.get() doesn't fail fast for errors like tls handshake error #227

Description

@kzhui125

without database pool

It will get error "Error: Pool(Backend(Io { kind: UnexpectedEof, message: "tls handshake eof" }))" immediately when connecting to SQL Server 2014 on macos.

tiberius-rs/tiberius#364

use deadpool_tiberius;

#[tokio::main]
async fn main() -> deadpool_tiberius::SqlServerResult<()> {
    let pool = deadpool_tiberius::Manager::new()
        .host("192.168.50.74") // default to localhost
        .port(1433) // default to
        .basic_authentication("user", "password")
        //  or .authentication(tiberius::AuthMethod)
        .database("test")
        .trust_cert()
        .max_size(10)
        .wait_timeout(15)
        .pre_recycle_sync(|_client, _metrics| Ok(()))
        .create_pool()?;

    let mut conn = pool.get().await?;
    let _rows = conn.simple_query("SELECT 1").await.unwrap();

    Ok(())
}
[dependencies]
deadpool = "0.12"
deadpool-tiberius = { version = "0.1", default-features = false, features = [
    "rustls",
    # "native-tls",
    # "vendored-openssl",
] }
futures = "0.3"
futures-core = "0.3"
tiberius = { version = "0.12", default-features = false, features = [
    "rustls",
    # "native-tls",
    # "vendored-openssl",
    "tds73",
] }
tokio = { version = "1", features = ["full"] }
tokio-util = { version = "0.7", features = ["compat"] }

using deadpool

I will get error immediately: Error: Pool(Backend(Io { kind: UnexpectedEof, message: "tls handshake eof" }))

use deadpool_tiberius;

#[tokio::main]
async fn main() -> deadpool_tiberius::SqlServerResult<()> {
    let pool = deadpool_tiberius::Manager::new()
        .host("192.168.50.74") // default to localhost
        .port(1433) // default to
        .basic_authentication("user", "password")
        //  or .authentication(tiberius::AuthMethod)
        .database("test")
        .trust_cert()
        .max_size(10)
        .wait_timeout(15)
        .pre_recycle_sync(|_client, _metrics| Ok(()))
        .create_pool()?;

    let mut conn = pool.get().await?;
    let _rows = conn.simple_query("SELECT 1").await.unwrap();

    Ok(())
}

using bb8

It will not return error immediately, it will ignore the error in bb8::ManageConnection.connect.

This is not expected, since I use pool.get to get connection then query something to check the database is healthy at program startup, it should return error immediately when tls handshake eof error occurs.

bb8 = "0.8"
use bb8::Pool;
use thiserror::Error;
use tiberius::{Client, Config};
use tokio::net::TcpStream;
use tokio_util::compat::{Compat, TokioAsyncWriteCompatExt};

#[derive(Error, Debug)]
pub enum DatabaseConnectionError {
    #[error("Failed to parse connection string: {0}")]
    ParseConnectionString(String),

    #[error("Failed to create database pool: {0}")]
    CreatePool(String),

    #[error("Database query error: {0}")]
    QueryError(#[from] tiberius::error::Error),

    #[error("Other database error: {0}")]
    Other(String),
}

pub type DbPool = Pool<TiberiusConnectionManager>;

pub struct TiberiusConnectionManager {
    config: Config,
}

impl TiberiusConnectionManager {
    pub fn new(config: Config) -> Self {
        Self { config }
    }
}

#[async_trait::async_trait]
impl bb8::ManageConnection for TiberiusConnectionManager {
    type Connection = Client<Compat<TcpStream>>;
    type Error = tiberius::error::Error;

    async fn connect(&self) -> Result<Self::Connection, Self::Error> {
        connect(&self.config).await
    }

    async fn is_valid(&self, conn: &mut Self::Connection) -> Result<(), Self::Error> {
        conn.simple_query("SELECT 1").await.map(|_| ())
    }

    fn has_broken(&self, _: &mut Self::Connection) -> bool {
        false
    }
}

pub async fn connect(
    config: &tiberius::Config,
) -> Result<Client<Compat<TcpStream>>, tiberius::error::Error> {
    let tcp = TcpStream::connect(config.get_addr()).await?;
    tcp.set_nodelay(true)?;
    Client::connect(config.clone(), tcp.compat_write()).await
}

pub async fn create_db_pool(
    connection_string: &str,
    max_pool_size: u32,
) -> Result<DbPool, DatabaseConnectionError> {
    let mut config = tiberius::Config::from_ado_string(connection_string).map_err(|e| {
        DatabaseConnectionError::ParseConnectionString(format!(
            "Failed to parse connection string: {}",
            e
        ))
    })?;
    config.trust_cert();
    let manager = TiberiusConnectionManager::new(config);
    Pool::builder()
        .max_size(max_pool_size)
        .build(manager)
        .await
        .map_err(|e| {
            DatabaseConnectionError::CreatePool(format!("Failed to create database pool: {}", e))
        })
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions