Skip to main content

klukai_types/sqlite_pool/
mod.rs

1mod config;
2
3use arc_swap::ArcSwap;
4use std::{
5    fmt,
6    ops::{Deref, DerefMut},
7    sync::{
8        Arc,
9        atomic::{AtomicUsize, Ordering},
10    },
11};
12use tracing::warn;
13
14use deadpool::managed::{self, Object};
15use metrics::counter;
16use rusqlite::{CachedStatement, InterruptHandle, Params, Transaction};
17use tokio::time::{Duration, sleep};
18use tokio_util::sync::CancellationToken;
19
20pub use deadpool::managed::reexports::*;
21pub use rusqlite;
22
23pub type Pool<T> = deadpool::managed::Pool<Manager<T>>;
24pub type RusqlitePool = Pool<rusqlite::Connection>;
25pub type CreatePoolError = deadpool::managed::CreatePoolError<ConfigError>;
26pub type PoolBuilder<T> = deadpool::managed::PoolBuilder<Manager<T>, Object<Manager<T>>>;
27pub type PoolError = deadpool::managed::PoolError<rusqlite::Error>;
28
29pub type Hook<T> = deadpool::managed::Hook<Manager<T>>;
30pub type HookError = deadpool::managed::HookError<rusqlite::Error>;
31
32pub type Connection<T> = deadpool::managed::Object<Manager<T>>;
33pub type RusqliteConnection = Connection<rusqlite::Connection>;
34
35#[inline]
36pub fn noop_transform(conn: rusqlite::Connection) -> rusqlite::Result<rusqlite::Connection> {
37    Ok(conn)
38}
39
40pub use self::config::{Config, ConfigError};
41
42pub type TransformFn<T> = dyn Fn(rusqlite::Connection) -> Result<T, rusqlite::Error> + Send + Sync;
43
44/// [`Manager`] for creating and recycling SQLite [`Connection`]s.
45///
46/// [`Manager`]: managed::Manager
47pub struct Manager<T> {
48    config: Config,
49    recycle_count: AtomicUsize,
50    transform: Box<TransformFn<T>>,
51}
52
53impl<T> Manager<T> {
54    /// Creates a new [`Manager`] using the given [`Config`] backed by the
55    /// specified [`Runtime`].
56    #[must_use]
57    pub fn from_config(
58        config: &Config,
59        transform: impl Fn(rusqlite::Connection) -> Result<T, rusqlite::Error> + Send + Sync + 'static,
60    ) -> Self {
61        Self {
62            config: config.clone(),
63            recycle_count: AtomicUsize::new(0),
64            transform: Box::new(transform),
65        }
66    }
67}
68
69impl<T> fmt::Debug for Manager<T> {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        f.debug_struct("Manager")
72            .field("config", &self.config)
73            .field("recycle_count", &self.recycle_count)
74            .finish()
75    }
76}
77
78pub trait SqliteConn: Send {
79    fn conn(&self) -> &rusqlite::Connection;
80}
81
82impl SqliteConn for rusqlite::Connection {
83    fn conn(&self) -> &rusqlite::Connection {
84        self
85    }
86}
87
88impl<T> managed::Manager for Manager<T>
89where
90    T: SqliteConn,
91{
92    type Type = T;
93    type Error = rusqlite::Error;
94
95    fn create(&self) -> impl std::future::Future<Output = Result<Self::Type, Self::Error>> + Send {
96        let config_path = self.config.path.clone();
97        let config_open_flags = self.config.open_flags;
98        let transform = &self.transform;
99
100        async move {
101            transform(rusqlite::Connection::open_with_flags(
102                &config_path,
103                config_open_flags,
104            )?)
105        }
106    }
107
108    fn recycle(
109        &self,
110        _conn: &mut Self::Type,
111        _: &Metrics,
112    ) -> impl std::future::Future<Output = managed::RecycleResult<Self::Error>> + Send {
113        let recycle_count = &self.recycle_count;
114
115        async move {
116            let _ = recycle_count.fetch_add(1, Ordering::Relaxed);
117            Ok(())
118        }
119    }
120}
121
122pub struct InterruptibleTransaction<T> {
123    conn: T,
124    timeout: Option<Duration>,
125    interrupt_hdl: Arc<InterruptHandle>,
126    source: &'static str,
127    current_sql: Arc<ArcSwap<Option<String>>>,
128}
129
130impl<T> InterruptibleTransaction<T>
131where
132    T: Deref<Target = rusqlite::Connection> + Committable,
133{
134    pub fn new(conn: T, timeout: Option<Duration>, source: &'static str) -> Self {
135        let interrupt_hdl = conn.get_interrupt_handle();
136
137        Self {
138            conn,
139            timeout,
140            interrupt_hdl: Arc::new(interrupt_hdl),
141            source,
142            current_sql: Arc::new(ArcSwap::new(Arc::new(None))),
143        }
144    }
145
146    pub fn execute(
147        &self,
148        sql: &str,
149        params: &[&dyn rusqlite::ToSql],
150    ) -> Result<usize, rusqlite::Error> {
151        let token = self.interrupt_on_timeout();
152        self.current_sql.store(Arc::new(Some(sql.to_string())));
153        let res = self.conn.execute(sql, params);
154        token.cancel();
155        res
156    }
157
158    pub fn commit(self) -> Result<(), rusqlite::Error> {
159        let token = self.interrupt_on_timeout();
160
161        let res = self.conn.commit();
162        token.cancel();
163        res
164    }
165
166    pub fn prepare(
167        &self,
168        sql: &str,
169    ) -> Result<InterruptibleStatement<Statement<'_>>, rusqlite::Error> {
170        let stmt = self.conn.prepare(sql)?;
171        self.current_sql.store(Arc::new(Some(sql.to_string())));
172        Ok(InterruptibleStatement::new(
173            Statement(stmt),
174            self.interrupt_hdl.clone(),
175            self.timeout,
176            sql.to_string(),
177        ))
178    }
179
180    pub fn prepare_cached(
181        &self,
182        sql: &str,
183    ) -> Result<InterruptibleStatement<CachedStatement<'_>>, rusqlite::Error> {
184        let stmt = self.conn.prepare_cached(sql)?;
185        self.current_sql.store(Arc::new(Some(sql.to_string())));
186        Ok(InterruptibleStatement::new(
187            stmt,
188            self.interrupt_hdl.clone(),
189            self.timeout,
190            sql.to_string(),
191        ))
192    }
193
194    pub fn execute_batch(&self, sql: &str) -> Result<(), rusqlite::Error> {
195        let token = self.interrupt_on_timeout();
196        self.current_sql.store(Arc::new(Some(sql.to_string())));
197        let res = self.conn.execute_batch(sql);
198        token.cancel();
199        res
200    }
201
202    pub fn savepoint(
203        &mut self,
204    ) -> Result<InterruptibleTransaction<rusqlite::Savepoint<'_>>, rusqlite::Error> {
205        let sp = self.conn.savepoint()?;
206        Ok(InterruptibleTransaction::new(sp, self.timeout, self.source))
207    }
208
209    pub fn interrupt_on_timeout(&self) -> CancellationToken {
210        let token = CancellationToken::new();
211        if let Some(timeout) = self.timeout {
212            let cloned_token = token.clone();
213            let interrupt_hdl = self.interrupt_hdl.clone();
214            let source = self.source;
215            let current_sql = self.current_sql.load();
216            tokio::spawn(async move {
217                tokio::select! {
218                    _ = cloned_token.cancelled() => {}
219                    _ = sleep(timeout) => {
220                        warn!("sql call took more than {timeout:?}, interrupting.. {:?}", current_sql);
221                        interrupt_hdl.interrupt();
222                        counter!("corro.sqlite.interrupt", "source" => source, "reason" => "timeout").increment(1);
223                    }
224                };
225            });
226        }
227
228        token
229    }
230
231    pub fn interrupt_on_cancel(&self, cancel: CancellationToken) {
232        let interrupt_hdl = self.interrupt_hdl.clone();
233        let source = self.source;
234        // let current_sql = self.current_sql.clone();
235        tokio::spawn(async move {
236            cancel.cancelled().await;
237            // let sql = current_sql.load();
238            // we have sensu checks makes flyd constantly connect to corrosion
239            // pg and this would spam the logs.
240            // warn!("interrupting sqlite connection - sql - {:?})", sql);
241            interrupt_hdl.interrupt();
242            counter!("corro.sqlite.interrupt", "source" => source, "reason" => "cancellation")
243                .increment(1);
244        });
245    }
246}
247
248impl<T> Deref for InterruptibleTransaction<T>
249where
250    T: Deref<Target = rusqlite::Connection>,
251{
252    type Target = rusqlite::Connection;
253
254    fn deref(&self) -> &Self::Target {
255        &self.conn
256    }
257}
258
259impl<T> DerefMut for InterruptibleTransaction<T>
260where
261    T: DerefMut<Target = rusqlite::Connection>,
262{
263    fn deref_mut(&mut self) -> &mut Self::Target {
264        &mut self.conn
265    }
266}
267
268pub struct InterruptibleStatement<T> {
269    stmt: T,
270    timeout: Option<Duration>,
271    interrupt_hdl: Arc<InterruptHandle>,
272    sql: String,
273}
274
275impl<'conn, 'a, T> InterruptibleStatement<T>
276where
277    T: Deref<Target = rusqlite::Statement<'conn>> + DerefMut<Target = rusqlite::Statement<'conn>>,
278{
279    pub fn new(
280        stmt: T,
281        interrupt_hdl: Arc<InterruptHandle>,
282        timeout: Option<Duration>,
283        sql: String,
284    ) -> Self {
285        Self {
286            stmt,
287            timeout,
288            interrupt_hdl,
289            sql,
290        }
291    }
292
293    pub fn execute<P: Params>(&mut self, params: P) -> Result<usize, rusqlite::Error> {
294        let token = self.interrupt_on_timeout();
295        let res = self.stmt.execute(params);
296        token.cancel();
297        res
298    }
299
300    pub fn query<'rows, P: Params>(
301        &'a mut self,
302        params: P,
303    ) -> Result<rusqlite::Rows<'rows>, rusqlite::Error>
304    where
305        'conn: 'rows,
306        'a: 'rows,
307    {
308        let token = self.interrupt_on_timeout();
309        let res = self.stmt.query(params);
310        token.cancel();
311        res
312    }
313
314    pub fn interrupt_on_timeout(&self) -> CancellationToken {
315        let token = CancellationToken::new();
316        if let Some(timeout) = self.timeout {
317            let cloned_token = token.clone();
318            let interrupt_hdl = self.interrupt_hdl.clone();
319            let current_sql = self.sql.clone();
320            tokio::spawn(async move {
321                tokio::select! {
322                    _ = cloned_token.cancelled() => {}
323                    _ = sleep(timeout) => {
324                        warn!("sql call took more than {timeout:?}, interrupting.. sql({:?})", current_sql);
325                        interrupt_hdl.interrupt();
326                        counter!("corro.sqlite.interrupt", "source" => "timeout").increment(1);
327                    }
328                };
329            });
330        }
331
332        token
333    }
334}
335
336impl<'conn, T: Deref<Target = rusqlite::Statement<'conn>>> Deref for InterruptibleStatement<T> {
337    type Target = rusqlite::Statement<'conn>;
338
339    fn deref(&self) -> &Self::Target {
340        &self.stmt
341    }
342}
343
344impl<'conn, T: DerefMut<Target = rusqlite::Statement<'conn>>> DerefMut
345    for InterruptibleStatement<T>
346{
347    fn deref_mut(&mut self) -> &mut Self::Target {
348        &mut self.stmt
349    }
350}
351
352pub trait Committable {
353    fn commit(self) -> Result<(), rusqlite::Error>;
354    fn savepoint(&mut self) -> Result<rusqlite::Savepoint<'_>, rusqlite::Error>;
355}
356
357impl Committable for Transaction<'_> {
358    fn commit(self) -> Result<(), rusqlite::Error> {
359        self.commit()
360    }
361
362    fn savepoint(&mut self) -> Result<rusqlite::Savepoint<'_>, rusqlite::Error> {
363        self.savepoint()
364    }
365}
366
367impl Committable for rusqlite::Savepoint<'_> {
368    fn commit(self) -> Result<(), rusqlite::Error> {
369        self.commit()
370    }
371
372    fn savepoint(&mut self) -> Result<rusqlite::Savepoint<'_>, rusqlite::Error> {
373        self.savepoint()
374    }
375}
376
377// No-op for plain connections
378impl Committable for rusqlite::Connection {
379    fn commit(self) -> Result<(), rusqlite::Error> {
380        Ok(())
381    }
382
383    fn savepoint(&mut self) -> Result<rusqlite::Savepoint<'_>, rusqlite::Error> {
384        Err(rusqlite::Error::ModuleError(String::from(
385            "cannot create savepoint from connection",
386        )))
387    }
388}
389
390pub struct Statement<'conn>(pub rusqlite::Statement<'conn>);
391
392impl<'conn> Deref for Statement<'conn> {
393    type Target = rusqlite::Statement<'conn>;
394
395    fn deref(&self) -> &Self::Target {
396        &self.0
397    }
398}
399
400impl<'conn> DerefMut for Statement<'conn> {
401    fn deref_mut(&mut self) -> &mut Self::Target {
402        &mut self.0
403    }
404}
405
406#[cfg(test)]
407mod tests {}