From 08465fb8470858457c878eef3934bb867d6725b8 Mon Sep 17 00:00:00 2001 From: rzuasti Date: Tue, 17 Feb 2026 15:20:47 -0500 Subject: [PATCH] Migrated db connections to pool --- backend/Cargo.lock | 307 +++++++++++++++++++++++++++++++++++++- backend/Cargo.toml | 5 +- backend/src/db.rs | 81 +++++----- backend/src/db/devices.rs | 16 +- backend/src/main.rs | 3 + backend/src/scanner.rs | 15 +- 6 files changed, 369 insertions(+), 58 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 7a2eff8..2dffdbe 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -85,6 +85,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "anyhow" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" + [[package]] name = "arraydeque" version = "0.5.1" @@ -545,6 +551,12 @@ dependencies = [ "log", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "erased-serde" version = "0.4.9" @@ -745,6 +757,19 @@ dependencies = [ "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if 1.0.4", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + [[package]] name = "gimli" version = "0.32.3" @@ -769,7 +794,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap", + "indexmap 1.9.3", "slab", "tokio 0.2.25", "tokio-util 0.3.1", @@ -1093,6 +1118,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "idna" version = "1.1.0" @@ -1143,6 +1174,18 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + [[package]] name = "iovec" version = "0.1.4" @@ -1246,6 +1289,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.180" @@ -1493,6 +1542,8 @@ dependencies = [ "log", "pnet", "pushover", + "r2d2", + "r2d2_sqlite", "rusqlite", "rusqlite_migration", "serde", @@ -1818,6 +1869,25 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.105" @@ -1858,6 +1928,57 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r2d2" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +dependencies = [ + "log", + "parking_lot 0.12.5", + "scheduled-thread-pool", +] + +[[package]] +name = "r2d2_sqlite" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ebd03c29250cdf191da93a35118b4567c2ef0eacab54f65e058d6f4c9965f6" +dependencies = [ + "r2d2", + "rusqlite", + "uuid", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "redox_syscall" version = "0.1.57" @@ -1980,9 +2101,9 @@ dependencies = [ [[package]] name = "rusqlite_migration" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08e45ef6076d02cdc5b6daa61999bb6a90d7a2e256c178c31ab903d5023f1493" +checksum = "67ffe0efe8568c769d1e39c67ea2a093134afaac9bb4559246a2ee7a4d686044" dependencies = [ "include_dir", "log", @@ -2011,7 +2132,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver", + "semver 0.9.0", ] [[package]] @@ -2048,6 +2169,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "scheduled-thread-pool" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +dependencies = [ + "parking_lot 0.12.5", +] + [[package]] name = "scoped-tls" version = "0.1.2" @@ -2092,6 +2222,12 @@ dependencies = [ "semver-parser", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "semver-parser" version = "0.7.0" @@ -2810,6 +2946,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "url" version = "2.5.8" @@ -2840,6 +2982,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +dependencies = [ + "getrandom 0.4.1", + "js-sys", + "rand", + "wasm-bindgen", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -2876,6 +3030,15 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.108" @@ -2937,6 +3100,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.10.0", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver 1.0.27", +] + [[package]] name = "web-sys" version = "0.3.85" @@ -3146,6 +3343,88 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.13.0", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.10.0", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver 1.0.27", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" @@ -3197,6 +3476,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.6" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 7d0f290..47648aa 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -11,8 +11,7 @@ tokio = { version = "1.0", features = ["full"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" rusqlite = { version = "0.38.0", features = ["bundled", "chrono"] } -rusqlite_migration = { version = "2.4.0", features = ["from-directory"] } -rusqlite_pool = "0.2.0" +rusqlite_migration = { version = "2.4.1", features = ["from-directory"] } include_dir = "0.7.4" chrono = {version = "0.4.43", features = ["serde"]} pushover = "0.4.0" @@ -22,3 +21,5 @@ duration-string = { version="0.5.3", features = ["serde"] } clap = {version="4.5.54", features=["derive"]} axum = "0.8.8" tower-http = { version = "0.6.8", features = ["fs"] } +r2d2 = "0.8.10" +r2d2_sqlite = "0.32.0" diff --git a/backend/src/db.rs b/backend/src/db.rs index 943fef2..69c5655 100644 --- a/backend/src/db.rs +++ b/backend/src/db.rs @@ -1,50 +1,63 @@ pub mod devices; use include_dir::{Dir, include_dir}; +use lazy_static::lazy_static; use log::{debug, error}; -use rusqlite::Connection; +use r2d2::PooledConnection; +use r2d2_sqlite::SqliteConnectionManager; use rusqlite_migration::Migrations; -use std::result::Result; -use std::sync::LazyLock; +use tokio::sync::Mutex; use crate::settings; static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/database_migrations"); +static INITIALISED: Mutex = Mutex::const_new(false); -// Define migrations. These are applied atomically. -static MIGRATIONS: LazyLock> = - LazyLock::new(|| Migrations::from_directory(&MIGRATIONS_DIR).unwrap()); +lazy_static! { + // Define migrations. These are applied atomically. + static ref MIGRATIONS: Migrations<'static> = + Migrations::from_directory(&MIGRATIONS_DIR).unwrap(); -pub fn init_db() -> Result { - if settings::CONFIG.database.path.is_empty() { - error!("Database path not set. Make sure to define database.path in your config file."); - Err(format!( - "Database path not set. Make sure to define database.path in your config file." - )) - } else { - debug!("Opening database at {}.", settings::CONFIG.database.path); + // TODO : Move pool size to configuration file + static ref POOL: r2d2::Pool = r2d2::Pool::builder().max_size(10).build(r2d2_sqlite::SqliteConnectionManager::file(settings::CONFIG.database.path.as_str())).unwrap(); +} - let database_path = settings::CONFIG.database.path.clone(); +pub fn get_db_connection() -> PooledConnection { + let result = POOL.get(); - let mut conn = match Connection::open(database_path) { - Ok(value) => value, - Err(error) => { - error!("Error opening database (oott.db): {error}"); - return Err(format!("Error opening database (oott.db): {error}")); - } - }; - - debug!("Database open, executing migrations if needed."); - // Update the database schema, atomically - match MIGRATIONS.to_latest(&mut conn) { - Ok(_) => { - debug!("Database up to date."); - Ok(conn) - } - Err(error) => { - error!("Error updating database: {error}"); - Err(format!("Error updating database: {error}")) - } + match result { + Ok(value) => value, + Err(error) => { + error!("Error obtaining database connection from the pool: {error}"); + panic!("Error obtaining database connection from the pool: {error}"); } } } + +pub async fn init_db() -> Result<(), String> { + let mut initialised = INITIALISED.lock().await; + if *initialised { + return Ok(()); + } + + debug!("Getting database connection"); + + let mut conn = get_db_connection(); + + debug!("Executing database migrations if needed."); + + let result = match MIGRATIONS.to_latest(&mut conn) { + Ok(_) => { + debug!("Database up to date."); + Ok(()) + } + Err(error) => { + error!("Error updating database: {error}"); + Err(format!("Error updating database: {error}")) + } + }; + + *initialised = true; + + result +} diff --git a/backend/src/db/devices.rs b/backend/src/db/devices.rs index 585a571..bec0e48 100644 --- a/backend/src/db/devices.rs +++ b/backend/src/db/devices.rs @@ -1,11 +1,12 @@ use log::{debug, error}; -use rusqlite::{Connection, params}; -use std::sync::MutexGuard; +use rusqlite::params; -use crate::model::devices::Device; +use crate::{db, model::devices::Device}; // Read device from its MAC address -pub fn read(conn: MutexGuard, mac_address: String) -> Option { +pub fn read(mac_address: String) -> Option { + let conn = db::get_db_connection(); + let result: Result = conn.query_one( "SELECT mac_address, ipv4_address, vendor, last_seen FROM devices WHERE mac_address=?1", params![mac_address], @@ -33,7 +34,9 @@ pub fn read(conn: MutexGuard, mac_address: String) -> Option } } -pub fn insert(conn: MutexGuard, device: Device) -> Result<(), String> { +pub fn insert(device: Device) -> Result<(), String> { + let conn = db::get_db_connection(); + match conn.execute( "INSERT INTO devices (mac_address, ipv4_address, vendor, last_seen) VALUES (?1, ?2, ?3, ?4)", params![device.mac_address, device.ipv4_address, device.vendor, device.last_seen]) { @@ -48,7 +51,8 @@ pub fn insert(conn: MutexGuard, device: Device) -> Result<(), String } } -pub fn update(conn: MutexGuard, device: Device) -> Result<(), String> { +pub fn update(device: Device) -> Result<(), String> { + let conn = db::get_db_connection(); match conn.execute( "UPDATE devices SET ipv4_address=?1, vendor=?2, last_seen=?3 WHERE mac_address=?4", params![ diff --git a/backend/src/main.rs b/backend/src/main.rs index aebec5e..c4222a3 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -31,6 +31,9 @@ async fn main() -> Result<(), String> { // Now onto the important stuff info!("Starting up oott"); + // Initialize database + db::init_db().await?; + // Start the device scanner and web server (for API and UI) in parallel tokio::join!(scanner::scan(), web_server::serve()).0 } diff --git a/backend/src/scanner.rs b/backend/src/scanner.rs index 42036ba..4c7f775 100644 --- a/backend/src/scanner.rs +++ b/backend/src/scanner.rs @@ -3,13 +3,9 @@ use crate::device_finders; use crate::events; use crate::settings::CONFIG; use log::{debug, info}; -use std::sync::{Arc, Mutex}; use tokio::time::{Duration, sleep}; pub async fn scan() -> Result<(), String> { - // Get database connection - thread protected - let db_conn = Arc::new(Mutex::new(db::init_db().unwrap())); - loop { // Find online devices via ARP let devices = device_finders::arp::find(CONFIG.networking.interface.to_string()).await?; @@ -21,13 +17,8 @@ pub async fn scan() -> Result<(), String> { for device in devices.iter() { debug!("Online device found {}", device); - // Using just one connection for now, need to update if moved DB portion to multi-thread - // need to change to a clone if we need multiple threads - let db_conn_clone = Arc::clone(&db_conn); - // Read device from database - let recorded_device_result = - db::devices::read(db_conn_clone.lock().unwrap(), device.mac_address.clone()); + let recorded_device_result = db::devices::read(device.mac_address.clone()); match recorded_device_result { Some(recorded_device) => { @@ -36,7 +27,7 @@ pub async fn scan() -> Result<(), String> { "Device found in database {}. Updating to {}.", recorded_device, device ); - db::devices::update(db_conn_clone.lock().unwrap(), device.clone())?; + db::devices::update(device.clone())?; events::trigger_existing_device(recorded_device, device.clone()).ok(); // Ignoring errors here, do not stop loop if notification delivery fails } None => { @@ -46,7 +37,7 @@ pub async fn scan() -> Result<(), String> { device.mac_address ); - db::devices::insert(db_conn_clone.lock().unwrap(), device.clone())?; + db::devices::insert(device.clone())?; events::trigger_new_device(device.clone()).ok(); // Ignoring errors here, do not stop loop if notification delivery fails } };