diff --git a/CHANGELOG.md b/CHANGELOG.md index 098244902..b057be945 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Changes - use transaction in `Contact::add_or_lookup()` #4059 +- Organize the connection pool as a stack rather than a queue to ensure that + connection page cache is reused more often. #4065 ### Fixes diff --git a/Cargo.lock b/Cargo.lock index 39890839c..836a44375 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -848,7 +848,6 @@ dependencies = [ "bitflags", "chrono", "criterion", - "crossbeam-queue", "deltachat_derive", "email", "encoded-words", @@ -870,6 +869,7 @@ dependencies = [ "num-traits", "num_cpus", "once_cell", + "parking_lot", "percent-encoding", "pgp", "pretty_env_logger", diff --git a/Cargo.toml b/Cargo.toml index 9f85cf1f1..ecbab5a88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,6 @@ backtrace = "0.3" base64 = "0.21" bitflags = "1.3" chrono = { version = "0.4", default-features=false, features = ["clock", "std"] } -crossbeam-queue = "0.3" email = { git = "https://github.com/deltachat/rust-email", branch = "master" } encoded-words = { git = "https://github.com/async-email/encoded-words", branch = "master" } escaper = "0.1" @@ -58,6 +57,7 @@ num-derive = "0.3" num-traits = "0.2" once_cell = "1.17.0" percent-encoding = "2.2" +parking_lot = "0.12" pgp = { version = "0.9", default-features = false } pretty_env_logger = { version = "0.4", optional = true } qrcodegen = "1.7.0" diff --git a/src/sql/pool.rs b/src/sql/pool.rs index fc7bf05bf..b7459976a 100644 --- a/src/sql/pool.rs +++ b/src/sql/pool.rs @@ -1,10 +1,18 @@ -//! Connection pool. +//! # SQLite connection pool. +//! +//! The connection pool holds a number of SQLite connections and allows to allocate them. +//! When allocated connection is dropped, underlying connection is returned back to the pool. +//! +//! The pool is organized as a stack. It always allocates the most recently used connection. +//! Each SQLite connection has its own page cache, so allocating recently used connections +//! improves the performance compared to, for example, organizing the pool as a queue +//! and returning the least recently used connection each time. use std::ops::{Deref, DerefMut}; use std::sync::{Arc, Weak}; use anyhow::{Context, Result}; -use crossbeam_queue::ArrayQueue; +use parking_lot::Mutex; use rusqlite::Connection; use tokio::sync::{OwnedSemaphorePermit, Semaphore}; @@ -12,7 +20,7 @@ use tokio::sync::{OwnedSemaphorePermit, Semaphore}; #[derive(Debug)] struct InnerPool { /// Available connections. - connections: ArrayQueue, + connections: Mutex>, /// Counts the number of available connections. semaphore: Arc, @@ -23,7 +31,9 @@ impl InnerPool { /// /// The connection could be new or returned back. fn put(&self, connection: Connection) { - self.connections.force_push(connection); + let mut connections = self.connections.lock(); + connections.push(connection); + drop(connections); } } @@ -74,22 +84,19 @@ pub struct Pool { impl Pool { /// Creates a new connection pool. pub fn new(connections: Vec) -> Self { + let semaphore = Arc::new(Semaphore::new(connections.len())); let inner = Arc::new(InnerPool { - connections: ArrayQueue::new(connections.len()), - semaphore: Arc::new(Semaphore::new(connections.len())), + connections: Mutex::new(connections), + semaphore, }); - for connection in connections { - inner.connections.force_push(connection); - } Pool { inner } } /// Retrieves a connection from the pool. pub async fn get(&self) -> Result { let permit = self.inner.semaphore.clone().acquire_owned().await?; - let conn = self - .inner - .connections + let mut connections = self.inner.connections.lock(); + let conn = connections .pop() .context("got a permit when there are no connections in the pool")?; let conn = PooledConnection {