7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 1 | // Copyright 2025 The Matrix.org Foundation C.I.C. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 2 | // |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 4 | // you may not use this file except in compliance with the License. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 5 | // You may obtain a copy of the License at |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 6 | // |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 8 | // |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 9 | // Unless required by applicable law or agreed to in writing, software |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 12 | // See the License for the specific language governing permissions and |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 13 | // limitations under the License. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 14 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 15 | //! An implementation of `deadpool` for `rusqlite`. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 16 | //! |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 17 | //! Initially, we were using `deadpool-sqlite`, that is also using `rusqlite` as |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 18 | //! the SQLite interface. However, in the implementation of |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 19 | //! [`deadpool::managed::Manager`], when recycling an object (i.e. an SQLite |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 20 | //! connection), [a SQL query is run to detect whether the connection is still |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 21 | //! alive][connection-test]. It creates performance issues: |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 22 | //! |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 23 | //! 1. It runs a prepared SQL query, which has a non-negligle cost. Imagine each |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 24 | //! connection is used to run on average one query; when recycled, a second |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 25 | //! query was constantly run. Even if it's a simple query, it requires to |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 26 | //! prepare a statement, to run and to query it. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 27 | //! 2. The SQL query was run in a blocking task. Indeed, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 28 | //! `deadpool_runtime::spawn_blocking` is used (via |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 29 | //! `deadpool_sync::SyncWrapper::interact`), which includes [blocking the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 30 | //! thread, acquiring a lock][spawn_blocking] etc. All this has more |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 31 | //! performance cost. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 32 | //! |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 33 | //! Measures have shown it is a performance bottleneck for us, especially on |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 34 | //! Android. Why specifically on Android and not other systems? This is still |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 35 | //! unclear at the time of writing (2025-11-11), despites having spent several |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 36 | //! days digging and trying to find an answer to this question. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 37 | //! |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 38 | //! We have tried to use another approach to test the aliveness of the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 39 | //! connections without running queries. It has involved patching `rusqlite` to |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 40 | //! add more bindings to SQLite, and patching `deadpool` itself, but without any |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 41 | //! successful results. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 42 | //! |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 43 | //! Finally, we have started questioning the reason of this test: why testing |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 44 | //! whether the connection was still alive? After all, there is no reason a |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 45 | //! connection should die in our case: |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 46 | //! |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 47 | //! - all connections are local, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 48 | //! - all interactions are behind [WAL], which is local only, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 49 | //! - even if for an unknown reason the connection died, using it next time |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 50 | //! would create an error… exactly what would happen when recycling the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 51 | //! connection. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 52 | //! |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 53 | //! Consequently, we have created a new implementation of `deadpool` for |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 54 | //! `rusqlite` that doesn't test the aliveness of the connections when recycled. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 55 | //! We assume they are all alive. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 56 | //! |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 57 | //! This implementation is, at the time of writing (2025-11-11): |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 58 | //! |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 59 | //! - 3.5 times faster on Android than `deadpool-sqlite`, removing the lock and |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 60 | //! thread contention entirely, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 61 | //! - 2 times faster on iOS. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 62 | //! |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 63 | //! [connection-test]: https://github.com/deadpool-rs/deadpool/blob/d6f7d58756f0cc7bdd1f3d54d820c1332d67e4d5/crates/deadpool-sqlite/src/lib.rs#L80-L100 |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 64 | //! [spawn_blocking]: https://github.com/deadpool-rs/deadpool/blob/d6f7d58756f0cc7bdd1f3d54d820c1332d67e4d5/crates/deadpool-sync/src/lib.rs#L113-L131 |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 65 | //! [WAL]: https://www.sqlite.org/wal.html |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 66 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 67 | use std::{convert::Infallible, path::PathBuf, sync::Arc, time::Duration}; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 68 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 69 | pub use deadpool::managed::reexports::*; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 70 | use deadpool::managed::{self, Metrics, PoolConfig, RecycleError}; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 71 | use deadpool_sync::SyncWrapper; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 72 | use tokio::sync::Mutex; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 73 | use tracing::{info, warn}; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 74 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 75 | /// The default runtime used by `matrix-sdk-sqlite` for `deadpool`. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 76 | pub const RUNTIME: Runtime = Runtime::Tokio1; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 77 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 78 | deadpool::managed_reexports!( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 79 | "matrix-sdk-sqlite", |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 80 | Manager, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 81 | managed::Object<Manager>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 82 | rusqlite::Error, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 83 | Infallible |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 84 | ); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 85 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 86 | /// Type representing a connection to SQLite from the [`Pool`]. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 87 | pub type Connection = Object; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 88 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 89 | /// [`Manager`][managed::Manager] for creating and recycling SQLite |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 90 | /// [`Connection`]s. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 91 | #[derive(Debug)] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 92 | pub struct Manager { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 93 | pub(crate) database_path: PathBuf, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 94 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 95 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 96 | impl Manager { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 97 | /// Creates a new [`Manager`] for a database. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 98 | #[must_use] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 99 | pub fn new(database_path: PathBuf) -> Self { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 100 | Self { database_path } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 101 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 102 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 103 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 104 | impl managed::Manager for Manager { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 105 | type Type = SyncWrapper<rusqlite::Connection>; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 106 | type Error = rusqlite::Error; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 107 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 108 | async fn create(&self) -> Result<Self::Type, Self::Error> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 109 | let path = self.database_path.clone(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 110 | SyncWrapper::new(RUNTIME, move || rusqlite::Connection::open(path)).await |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 111 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 112 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 113 | async fn recycle( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 114 | &self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 115 | conn: &mut Self::Type, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 116 | _: &Metrics, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 117 | ) -> managed::RecycleResult<Self::Error> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 118 | if conn.is_mutex_poisoned() { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 119 | return Err(RecycleError::Message( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 120 | "Mutex is poisoned. Connection is considered unusable.".into(), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 121 | )); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 122 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 123 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 124 | Ok(()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 125 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 126 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 127 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 128 | /// Gracefully close the store-owned write connection. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 129 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 130 | /// Callers are expected to remove the enclosing [`SqliteConnections`] from the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 131 | /// store before calling this helper so no new write acquisitions can happen |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 132 | /// through the store API. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 133 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 134 | /// 1. Waits for any in-flight write to complete by acquiring the lock. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 135 | /// 2. Runs a WAL checkpoint (TRUNCATE) to flush pending data to the main |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 136 | /// database file and release WAL locks. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 137 | /// 3. Drops the store-owned `Arc` on a blocking thread. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 138 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 139 | /// This is still best effort: if another cloned `Arc` or an |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 140 | /// `OwnedMutexGuard<Connection>` is still alive elsewhere, the underlying |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 141 | /// SQLite connection may remain alive until that handle is dropped. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 142 | pub async fn close_connection(write_connection: Arc<Mutex<Connection>>) { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 143 | // Acquire the lock to wait for any in-flight write to complete. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 144 | let guard = write_connection.lock().await; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 145 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 146 | // Flush WAL and release locks while we still own the connection. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 147 | let _ = guard |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 148 | .interact(|raw| { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 149 | raw.execute_batch("PRAGMA locking_mode = NORMAL; PRAGMA wal_checkpoint(TRUNCATE);") |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 150 | .ok(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 151 | }) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 152 | .await; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 153 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 154 | drop(guard); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 155 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 156 | // Drop the store-owned Arc on a blocking thread. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 157 | let _ = tokio::task::spawn_blocking(move || drop(write_connection)).await; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 158 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 159 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 160 | /// Live database connections held by each SQLite store. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 161 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 162 | /// Wrapped in `Option<SqliteConnections>` guarded by a `Mutex` inside each |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 163 | /// store; `Some` means the store is active, `None` means it is closed. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 164 | pub(crate) struct SqliteConnections { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 165 | /// The pool of read connections. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 166 | pub pool: Pool, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 167 | /// The dedicated write connection. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 168 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 169 | /// This lives behind `Arc<Mutex<_>>` so stores can clone the `Arc` and |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 170 | /// obtain an `OwnedMutexGuard<Connection>` without holding the outer |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 171 | /// `connections` mutex across await points. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 172 | pub write_connection: Arc<Mutex<Connection>>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 173 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 174 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 175 | /// Close a store by taking its connections out. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 176 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 177 | /// After this returns, any new call to `read()` or `write()` through the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 178 | /// store will fail with [`crate::error::Error::StoreClosed`] until |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 179 | /// [`reopen_connections`] is called. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 180 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 181 | /// Idempotent: if the store is already closed this is a no-op. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 182 | pub(crate) async fn close_connections(connections: &Mutex<Option<SqliteConnections>>, label: &str) { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 183 | let mut guard = connections.lock().await; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 184 | let Some(conns) = guard.take() else { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 185 | // Already closed — idempotent. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 186 | return; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 187 | }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 188 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 189 | let SqliteConnections { pool, write_connection } = conns; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 190 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 191 | // Close the pool. Idle read connections are dropped immediately; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 192 | // in-flight reads complete and their connections are discarded (not |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 193 | // recycled) on release. New pool.get() calls return PoolError::Closed. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 194 | pool.close(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 195 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 196 | let status = pool.status(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 197 | info!( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 198 | size = status.size, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 199 | max_size = status.max_size, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 200 | available = status.available, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 201 | "{label} pause: pool closed" |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 202 | ); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 203 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 204 | // Close the write connection: wait for any in-flight write to finish, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 205 | // run a WAL checkpoint, then drop on a blocking thread. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 206 | close_connection(write_connection).await; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 207 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 208 | let status = pool.status(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 209 | info!( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 210 | size = status.size, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 211 | max_size = status.max_size, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 212 | available = status.available, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 213 | "{label} pause: write connection released" |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 214 | ); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 215 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 216 | // Wait for any in-flight read connections to drain. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 217 | // The write connection has already been released above, so |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 218 | // pool.status().size == 0 now correctly means every connection is gone |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 219 | // and no SQLite file locks are held. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 220 | let deadline = tokio::time::Instant::now() + Duration::from_secs(5); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 221 | while pool.status().size > 0 { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 222 | if tokio::time::Instant::now() >= deadline { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 223 | let status = pool.status(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 224 | warn!( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 225 | size = status.size, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 226 | max_size = status.max_size, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 227 | available = status.available, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 228 | "Timed out waiting for SQLite pool connections to drain" |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 229 | ); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 230 | break; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 231 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 232 | tokio::time::sleep(Duration::from_millis(50)).await; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 233 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 234 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 235 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 236 | /// Resume a store by rebuilding its connections. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 237 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 238 | /// Idempotent: if the store is already active this is a no-op. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 239 | pub(crate) async fn reopen_connections( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 240 | connections: &Mutex<Option<SqliteConnections>>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 241 | db_path: PathBuf, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 242 | pool_config: PoolConfig, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 243 | runtime_config: crate::RuntimeConfig, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 244 | ) -> crate::error::Result<()> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 245 | use crate::utils::SqliteAsyncConnExt as _; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 246 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 247 | let mut guard = connections.lock().await; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 248 | if guard.is_some() { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 249 | // Not closed — idempotent. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 250 | return Ok(()); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 251 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 252 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 253 | // Rebuild the pool (connections are created lazily on first get()). |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 254 | let pool = |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 255 | Pool::builder(Manager::new(db_path)).config(pool_config).runtime(RUNTIME).build().map_err( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 256 | |e| crate::error::Error::InvalidData { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 257 | details: format!("Failed to rebuild connection pool: {e}"), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 258 | }, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 259 | )?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 260 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 261 | let write_conn = pool.get().await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 262 | // Re-apply runtime config (WAL mode, busy timeout, etc.) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 263 | write_conn.apply_runtime_config(runtime_config).await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 264 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 265 | *guard = Some(SqliteConnections { pool, write_connection: Arc::new(Mutex::new(write_conn)) }); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 266 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 267 | Ok(()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 268 | } |