mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-07-05 20:41:58 +02:00
Fix startup failure from fd exhaustion when launched via Finder (#500)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Generated
+12
-2
@@ -3885,9 +3885,9 @@ checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
version = "0.2.186"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
@@ -6649,6 +6649,15 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rlimit"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f35ee2729c56bb610f6dba436bf78135f728b7373bdffae2ec815b2d3eb98cc3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rolldown"
|
||||
version = "0.1.0"
|
||||
@@ -10932,6 +10941,7 @@ dependencies = [
|
||||
"r2d2_sqlite",
|
||||
"rand 0.9.1",
|
||||
"reqwest 0.12.20",
|
||||
"rlimit",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
|
||||
@@ -24,6 +24,9 @@ tauri-build = { version = "2.6.1", features = [] }
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
openssl-sys = { version = "0.9.105", features = ["vendored"] } # For Ubuntu installation to work
|
||||
|
||||
[target.'cfg(any(target_os = "macos", target_os = "linux"))'.dependencies]
|
||||
rlimit = "0.11" # Raise the launchd 256 open-file soft limit at startup
|
||||
|
||||
[dependencies]
|
||||
charset = "0.1.5"
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
|
||||
@@ -1676,6 +1676,14 @@ async fn cmd_check_for_updates<R: Runtime>(
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
#[cfg_attr(feature = "cef", tauri::cef_entry_point)]
|
||||
pub fn run() {
|
||||
// GUI apps launched via Finder/launchd inherit a 256 open-file soft limit on macOS
|
||||
// (1024 on most Linux desktops). SQLite WAL connections hold ~3 fds each, so raise
|
||||
// the limit toward the hard cap before opening any DB pools.
|
||||
#[cfg(any(target_os = "macos", target_os = "linux"))]
|
||||
if let Err(e) = rlimit::increase_nofile_limit(10240) {
|
||||
eprintln!("Failed to raise open-file limit: {e}");
|
||||
}
|
||||
|
||||
let mut builder = tauri::Builder::<TauriRuntime>::default().plugin(
|
||||
Builder::default()
|
||||
.targets([
|
||||
|
||||
@@ -5,7 +5,6 @@ use log::{debug, info};
|
||||
use r2d2::Pool;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use rusqlite::{OptionalExtension, params};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
static BLOB_MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/blob_migrations");
|
||||
|
||||
@@ -25,23 +24,21 @@ impl BodyChunk {
|
||||
}
|
||||
|
||||
/// Manages the blob database connection pool.
|
||||
// Pool is internally synchronized — don't wrap it in a Mutex. A Mutex held across the
|
||||
// blocking `get()` serializes every blob access behind the slowest waiter, freezing the
|
||||
// whole app whenever the pool is exhausted.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BlobManager {
|
||||
pool: Arc<Mutex<Pool<SqliteConnectionManager>>>,
|
||||
pool: Pool<SqliteConnectionManager>,
|
||||
}
|
||||
|
||||
impl BlobManager {
|
||||
pub fn new(pool: Pool<SqliteConnectionManager>) -> Self {
|
||||
Self { pool: Arc::new(Mutex::new(pool)) }
|
||||
Self { pool }
|
||||
}
|
||||
|
||||
pub fn connect(&self) -> BlobContext {
|
||||
let conn = self
|
||||
.pool
|
||||
.lock()
|
||||
.expect("Failed to gain lock on blob DB")
|
||||
.get()
|
||||
.expect("Failed to get blob DB connection from pool");
|
||||
let conn = self.pool.get().expect("Failed to get blob DB connection from pool");
|
||||
BlobContext { conn }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,11 +54,15 @@ pub fn init_standalone(
|
||||
create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
// Main database pool
|
||||
// Main database pool. Sized for concurrent in-flight queries, not concurrent app
|
||||
// features — connections are held per-statement, so even heavy fan-out (e.g. many
|
||||
// gRPC streams) only needs a handful at once. Keep max_size modest: WAL connections
|
||||
// hold ~3 file descriptors each, and macOS GUI apps get a 256 fd soft limit.
|
||||
info!("Initializing app database {db_path:?}");
|
||||
let manager = sqlite_file_manager(db_path);
|
||||
let pool = Pool::builder()
|
||||
.max_size(100)
|
||||
.max_size(20)
|
||||
.min_idle(Some(2))
|
||||
.connection_timeout(Duration::from_secs(10))
|
||||
.build(manager)
|
||||
.map_err(|e| Error::Database(e.to_string()))?;
|
||||
@@ -70,7 +74,8 @@ pub fn init_standalone(
|
||||
// Blob database pool
|
||||
let blob_manager = sqlite_file_manager(blob_path);
|
||||
let blob_pool = Pool::builder()
|
||||
.max_size(50)
|
||||
.max_size(10)
|
||||
.min_idle(Some(1))
|
||||
.connection_timeout(Duration::from_secs(10))
|
||||
.build(blob_manager)
|
||||
.map_err(|e| Error::Database(e.to_string()))?;
|
||||
|
||||
@@ -4,27 +4,25 @@ use crate::util::ModelPayload;
|
||||
use r2d2::Pool;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use rusqlite::TransactionBehavior;
|
||||
use std::sync::{Arc, Mutex, mpsc};
|
||||
use std::sync::mpsc;
|
||||
use yaak_database::{ConnectionOrTx, DbContext};
|
||||
|
||||
// Pool is internally synchronized — don't wrap it in a Mutex. A Mutex held across the
|
||||
// blocking `get()` serializes every DB access behind the slowest waiter, freezing the
|
||||
// whole app whenever the pool is exhausted.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct QueryManager {
|
||||
pool: Arc<Mutex<Pool<SqliteConnectionManager>>>,
|
||||
pool: Pool<SqliteConnectionManager>,
|
||||
events_tx: mpsc::Sender<ModelPayload>,
|
||||
}
|
||||
|
||||
impl QueryManager {
|
||||
pub fn new(pool: Pool<SqliteConnectionManager>, events_tx: mpsc::Sender<ModelPayload>) -> Self {
|
||||
QueryManager { pool: Arc::new(Mutex::new(pool)), events_tx }
|
||||
QueryManager { pool, events_tx }
|
||||
}
|
||||
|
||||
pub fn connect(&self) -> ClientDb<'_> {
|
||||
let conn = self
|
||||
.pool
|
||||
.lock()
|
||||
.expect("Failed to gain lock on DB")
|
||||
.get()
|
||||
.expect("Failed to get a new DB connection from the pool");
|
||||
let conn = self.pool.get().expect("Failed to get a new DB connection from the pool");
|
||||
let ctx = DbContext::new(ConnectionOrTx::Connection(conn));
|
||||
ClientDb::new(ctx, self.events_tx.clone())
|
||||
}
|
||||
@@ -33,12 +31,7 @@ impl QueryManager {
|
||||
where
|
||||
F: FnOnce(&ClientDb) -> T,
|
||||
{
|
||||
let conn = self
|
||||
.pool
|
||||
.lock()
|
||||
.expect("Failed to gain lock on DB for transaction")
|
||||
.get()
|
||||
.expect("Failed to get new DB connection from the pool");
|
||||
let conn = self.pool.get().expect("Failed to get new DB connection from the pool");
|
||||
|
||||
let ctx = DbContext::new(ConnectionOrTx::Connection(conn));
|
||||
let db = ClientDb::new(ctx, self.events_tx.clone());
|
||||
@@ -53,12 +46,7 @@ impl QueryManager {
|
||||
where
|
||||
E: From<crate::error::Error>,
|
||||
{
|
||||
let mut conn = self
|
||||
.pool
|
||||
.lock()
|
||||
.expect("Failed to gain lock on DB for transaction")
|
||||
.get()
|
||||
.expect("Failed to get new DB connection from the pool");
|
||||
let mut conn = self.pool.get().expect("Failed to get new DB connection from the pool");
|
||||
let tx = conn
|
||||
.transaction_with_behavior(TransactionBehavior::Immediate)
|
||||
.expect("Failed to start DB transaction");
|
||||
|
||||
Reference in New Issue
Block a user