From 3135a15cb38d96268a19edd738ff3165a735b56b Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Mon, 4 May 2026 22:10:19 +0200 Subject: [PATCH] fix: WAL mode + busy_timeout for SQLite, fix rate limiter TOCTOU race --- crates/presentation/src/main.rs | 4 +++- crates/presentation/src/routes.rs | 13 +++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/presentation/src/main.rs b/crates/presentation/src/main.rs index ecce9fb..db1ed5c 100644 --- a/crates/presentation/src/main.rs +++ b/crates/presentation/src/main.rs @@ -48,7 +48,9 @@ async fn wire_dependencies() -> anyhow::Result { let database_url = std::env::var("DATABASE_URL").context("DATABASE_URL must be set")?; let opts = SqliteConnectOptions::from_str(&database_url) .context("Invalid DATABASE_URL")? - .create_if_missing(true); + .create_if_missing(true) + .journal_mode(sqlx::sqlite::SqliteJournalMode::Wal) + .busy_timeout(std::time::Duration::from_secs(5)); let pool = SqlitePool::connect_with(opts) .await .context("Failed to connect to SQLite database")?; diff --git a/crates/presentation/src/routes.rs b/crates/presentation/src/routes.rs index a574544..190f9f2 100644 --- a/crates/presentation/src/routes.rs +++ b/crates/presentation/src/routes.rs @@ -35,14 +35,15 @@ impl RateLimiter { .unwrap_or_default() .as_secs() / 60; - let prev = self.window.load(Ordering::Relaxed); + let prev = self.window.load(Ordering::Acquire); if now != prev { - self.window.store(now, Ordering::Relaxed); - self.count.store(1, Ordering::Relaxed); - true - } else { - self.count.fetch_add(1, Ordering::Relaxed) + 1 <= self.limit + // compare_exchange ensures only one thread wins the window reset + if self.window.compare_exchange(prev, now, Ordering::AcqRel, Ordering::Relaxed).is_ok() { + self.count.store(1, Ordering::Release); + return true; + } } + self.count.fetch_add(1, Ordering::Relaxed) + 1 <= self.limit } }