add stuff and bits
This commit is contained in:
		
							parent
							
								
									a50abeedad
								
							
						
					
					
						commit
						0f2dc9a239
					
				
					 2 changed files with 148 additions and 32 deletions
				
			
		|  | @ -53,6 +53,10 @@ pub struct Config { | |||
|     sqlite_wal_clean_second_interval: u32, | ||||
|     #[serde(default = "default_sqlite_wal_clean_second_timeout")] | ||||
|     sqlite_wal_clean_second_timeout: u32, | ||||
|     #[serde(default = "default_sqlite_spillover_reap_chunk")] | ||||
|     sqlite_spillover_reap_chunk: u32, | ||||
|     #[serde(default = "default_sqlite_spillover_reap_interval_secs")] | ||||
|     sqlite_spillover_reap_interval_secs: u32, | ||||
|     #[serde(default = "default_max_request_size")] | ||||
|     max_request_size: u32, | ||||
|     #[serde(default = "default_max_concurrent_requests")] | ||||
|  | @ -121,6 +125,14 @@ fn default_sqlite_wal_clean_second_timeout() -> u32 { | |||
|     2 | ||||
| } | ||||
| 
 | ||||
| fn default_sqlite_spillover_reap_chunk() -> u32 { | ||||
|     5 | ||||
| } | ||||
| 
 | ||||
| fn default_sqlite_spillover_reap_interval_secs() -> u32 { | ||||
|     10 | ||||
| } | ||||
| 
 | ||||
| fn default_max_request_size() -> u32 { | ||||
|     20 * 1024 * 1024 // Default to 20 MB
 | ||||
| } | ||||
|  | @ -420,7 +432,10 @@ impl Database { | |||
|         drop(guard); | ||||
| 
 | ||||
|         #[cfg(feature = "sqlite")] | ||||
|         { | ||||
|             Self::start_wal_clean_task(&db, &config).await; | ||||
|             Self::start_spillover_reap_task(builder, &config).await; | ||||
|         } | ||||
| 
 | ||||
|         Ok(db) | ||||
|     } | ||||
|  | @ -541,6 +556,35 @@ impl Database { | |||
|         self._db.flush_wal() | ||||
|     } | ||||
| 
 | ||||
|     #[cfg(feature = "sqlite")] | ||||
|     pub async fn start_spillover_reap_task(engine: Arc<Engine>, config: &Config) { | ||||
|         let chunk_size = match config.sqlite_spillover_reap_chunk { | ||||
|             0 => None, // zero means no chunking, reap everything
 | ||||
|             a @ _ => Some(a), | ||||
|         }; | ||||
|         let interval_secs = config.sqlite_spillover_reap_interval_secs as u64; | ||||
| 
 | ||||
|         let weak = Arc::downgrade(&engine); | ||||
| 
 | ||||
|         tokio::spawn(async move { | ||||
|             use tokio::time::interval; | ||||
| 
 | ||||
|             use std::{sync::Weak, time::Duration}; | ||||
| 
 | ||||
|             let mut i = interval(Duration::from_secs(interval_secs)); | ||||
| 
 | ||||
|             loop { | ||||
|                 i.tick().await; | ||||
| 
 | ||||
|                 if let Some(arc) = Weak::upgrade(&weak) { | ||||
|                     arc.reap_spillover(chunk_size); | ||||
|                 } else { | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     #[cfg(feature = "sqlite")] | ||||
|     pub async fn start_wal_clean_task(lock: &Arc<TokioRwLock<Self>>, config: &Config) { | ||||
|         use tokio::time::{interval, timeout}; | ||||
|  |  | |||
|  | @ -1,3 +1,11 @@ | |||
| use super::{DatabaseEngine, Tree}; | ||||
| use crate::{database::Config, Result}; | ||||
| use crossbeam::channel::{ | ||||
|     bounded, unbounded, Receiver as ChannelReceiver, Sender as ChannelSender, TryRecvError, | ||||
| }; | ||||
| use log::debug; | ||||
| use parking_lot::{Mutex, MutexGuard, RwLock}; | ||||
| use rusqlite::{params, Connection, DatabaseName::Main, OptionalExtension}; | ||||
| use std::{ | ||||
|     collections::BTreeMap, | ||||
|     future::Future, | ||||
|  | @ -8,33 +16,12 @@ use std::{ | |||
|     thread, | ||||
|     time::{Duration, Instant}, | ||||
| }; | ||||
| 
 | ||||
| use crate::{database::Config, Result}; | ||||
| 
 | ||||
| use super::{DatabaseEngine, Tree}; | ||||
| 
 | ||||
| use log::debug; | ||||
| 
 | ||||
| use crossbeam::channel::{bounded, Sender as ChannelSender}; | ||||
| use parking_lot::{Mutex, MutexGuard, RwLock}; | ||||
| use rusqlite::{params, Connection, DatabaseName::Main, OptionalExtension}; | ||||
| 
 | ||||
| use tokio::sync::oneshot::Sender; | ||||
| 
 | ||||
| // const SQL_CREATE_TABLE: &str =
 | ||||
| //     "CREATE TABLE IF NOT EXISTS {} {{ \"key\" BLOB PRIMARY KEY, \"value\" BLOB NOT NULL }}";
 | ||||
| // const SQL_SELECT: &str = "SELECT value FROM {} WHERE key = ?";
 | ||||
| // const SQL_INSERT: &str = "INSERT OR REPLACE INTO {} (key, value) VALUES (?, ?)";
 | ||||
| // const SQL_DELETE: &str = "DELETE FROM {} WHERE key = ?";
 | ||||
| // const SQL_SELECT_ITER: &str = "SELECT key, value FROM {}";
 | ||||
| // const SQL_SELECT_PREFIX: &str = "SELECT key, value FROM {} WHERE key LIKE ?||'%' ORDER BY key ASC";
 | ||||
| // const SQL_SELECT_ITER_FROM_FORWARDS: &str = "SELECT key, value FROM {} WHERE key >= ? ORDER BY ASC";
 | ||||
| // const SQL_SELECT_ITER_FROM_BACKWARDS: &str =
 | ||||
| //     "SELECT key, value FROM {} WHERE key <= ? ORDER BY DESC";
 | ||||
| 
 | ||||
| struct Pool { | ||||
|     writer: Mutex<Connection>, | ||||
|     readers: Vec<Mutex<Connection>>, | ||||
|     spills: ConnectionRecycler, | ||||
|     spill_tracker: Arc<()>, | ||||
|     path: PathBuf, | ||||
| } | ||||
|  | @ -43,7 +30,7 @@ pub const MILLI: Duration = Duration::from_millis(1); | |||
| 
 | ||||
| enum HoldingConn<'a> { | ||||
|     FromGuard(MutexGuard<'a, Connection>), | ||||
|     FromOwned(Connection, Arc<()>), | ||||
|     FromRecycled(RecycledConn, Arc<()>), | ||||
| } | ||||
| 
 | ||||
| impl<'a> Deref for HoldingConn<'a> { | ||||
|  | @ -52,7 +39,57 @@ impl<'a> Deref for HoldingConn<'a> { | |||
|     fn deref(&self) -> &Self::Target { | ||||
|         match self { | ||||
|             HoldingConn::FromGuard(guard) => guard.deref(), | ||||
|             HoldingConn::FromOwned(conn, _) => conn, | ||||
|             HoldingConn::FromRecycled(conn, _) => conn.deref(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| struct ConnectionRecycler(ChannelSender<Connection>, ChannelReceiver<Connection>); | ||||
| 
 | ||||
| impl ConnectionRecycler { | ||||
|     fn new() -> Self { | ||||
|         let (s, r) = unbounded(); | ||||
|         Self(s, r) | ||||
|     } | ||||
| 
 | ||||
|     fn recycle(&self, conn: Connection) -> RecycledConn { | ||||
|         let sender = self.0.clone(); | ||||
| 
 | ||||
|         RecycledConn(Some(conn), sender) | ||||
|     } | ||||
| 
 | ||||
|     fn try_take(&self) -> Option<Connection> { | ||||
|         match self.1.try_recv() { | ||||
|             Ok(conn) => Some(conn), | ||||
|             Err(TryRecvError::Empty) => None, | ||||
|             // as this is pretty impossible, a panic is warranted if it ever occurs
 | ||||
|             Err(TryRecvError::Disconnected) => panic!("Receiving channel was disconnected. A a sender is owned by the current struct, this should never happen(!!!)") | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| struct RecycledConn( | ||||
|     Option<Connection>, // To allow moving out of the struct when `Drop` is called.
 | ||||
|     ChannelSender<Connection>, | ||||
| ); | ||||
| 
 | ||||
| impl Deref for RecycledConn { | ||||
|     type Target = Connection; | ||||
| 
 | ||||
|     fn deref(&self) -> &Self::Target { | ||||
|         self.0 | ||||
|             .as_ref() | ||||
|             .expect("RecycledConn does not have a connection in Option<>") | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Drop for RecycledConn { | ||||
|     fn drop(&mut self) { | ||||
|         if let Some(conn) = self.0.take() { | ||||
|             log::debug!("Recycled connection"); | ||||
|             if let Err(e) = self.1.send(conn) { | ||||
|                 log::warn!("Recycling a connection led to the following error: {:?}", e) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -76,6 +113,7 @@ impl Pool { | |||
|         Ok(Self { | ||||
|             writer, | ||||
|             readers, | ||||
|             spills: ConnectionRecycler::new(), | ||||
|             spill_tracker: Arc::new(()), | ||||
|             path: path.as_ref().to_path_buf(), | ||||
|         }) | ||||
|  | @ -104,24 +142,38 @@ impl Pool { | |||
|     } | ||||
| 
 | ||||
|     fn read_lock(&self) -> HoldingConn<'_> { | ||||
|         // First try to get a connection from the permanent pool
 | ||||
|         for r in &self.readers { | ||||
|             if let Some(reader) = r.try_lock() { | ||||
|                 return HoldingConn::FromGuard(reader); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         let spill_arc = self.spill_tracker.clone(); | ||||
|         // We didn't get a connection from the permanent pool, so we'll dumpster-dive for recycled connections.
 | ||||
|         // Either we have a connection or we dont, if we don't, we make a new one.
 | ||||
|         let conn = match self.spills.try_take() { | ||||
|             Some(conn) => conn, | ||||
|             None => Self::prepare_conn(&self.path, None).unwrap(), | ||||
|         }; | ||||
| 
 | ||||
|         // Clone the spill Arc to mark how many spilled connections actually exist.
 | ||||
|         let spill_arc = Arc::clone(&self.spill_tracker); | ||||
| 
 | ||||
|         // Get a sense of how many connections exist now.
 | ||||
|         let now_count = Arc::strong_count(&spill_arc) - 1 /* because one is held by the pool */; | ||||
| 
 | ||||
|         log::warn!("read_lock: all readers locked, creating spillover reader..."); | ||||
|         log::debug!("read_lock: all readers locked, creating spillover reader..."); | ||||
| 
 | ||||
|         if now_count > 1 { | ||||
|             log::warn!("read_lock: now {} spillover readers exist", now_count); | ||||
|         // If the spillover readers are more than the number of total readers, there might be a problem.
 | ||||
|         if now_count > self.readers.len() { | ||||
|             log::warn!( | ||||
|                 "read_lock: possible high load; now {} spillover readers exist", | ||||
|                 now_count | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         let spilled = Self::prepare_conn(&self.path, None).unwrap(); | ||||
| 
 | ||||
|         HoldingConn::FromOwned(spilled, spill_arc) | ||||
|         // Return the recyclable connection.
 | ||||
|         HoldingConn::FromRecycled(self.spills.recycle(conn), spill_arc) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -188,6 +240,26 @@ impl Engine { | |||
|             ) | ||||
|             .map_err(Into::into) | ||||
|     } | ||||
| 
 | ||||
|     // Reaps (at most) X amount of connections if `amount` is Some.
 | ||||
|     // If none, reaps all currently idle connections.
 | ||||
|     pub fn reap_spillover(&self, amount: Option<u32>) { | ||||
|         let mut reaped = 0; | ||||
| 
 | ||||
|         if let Some(amount) = amount { | ||||
|             for _ in 0..amount { | ||||
|                 if self.pool.spills.try_take().is_some() { | ||||
|                     reaped += 1; | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             while let Some(_) = self.pool.spills.try_take() { | ||||
|                 reaped += 1; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         log::debug!("Reaped {} connections", reaped); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct SqliteTable { | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue