klukai_types/sqlite_pool/
mod.rs1mod 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
44pub struct Manager<T> {
48 config: Config,
49 recycle_count: AtomicUsize,
50 transform: Box<TransformFn<T>>,
51}
52
53impl<T> Manager<T> {
54 #[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 tokio::spawn(async move {
236 cancel.cancelled().await;
237 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
377impl 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 {}