mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-07-06 04:45:24 +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]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.172"
|
version = "0.2.186"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libdbus-sys"
|
name = "libdbus-sys"
|
||||||
@@ -6649,6 +6649,15 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"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]]
|
[[package]]
|
||||||
name = "rolldown"
|
name = "rolldown"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -10932,6 +10941,7 @@ dependencies = [
|
|||||||
"r2d2_sqlite",
|
"r2d2_sqlite",
|
||||||
"rand 0.9.1",
|
"rand 0.9.1",
|
||||||
"reqwest 0.12.20",
|
"reqwest 0.12.20",
|
||||||
|
"rlimit",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri",
|
"tauri",
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ tauri-build = { version = "2.6.1", features = [] }
|
|||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
openssl-sys = { version = "0.9.105", features = ["vendored"] } # For Ubuntu installation to work
|
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]
|
[dependencies]
|
||||||
charset = "0.1.5"
|
charset = "0.1.5"
|
||||||
chrono = { workspace = true, features = ["serde"] }
|
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(mobile, tauri::mobile_entry_point)]
|
||||||
#[cfg_attr(feature = "cef", tauri::cef_entry_point)]
|
#[cfg_attr(feature = "cef", tauri::cef_entry_point)]
|
||||||
pub fn run() {
|
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(
|
let mut builder = tauri::Builder::<TauriRuntime>::default().plugin(
|
||||||
Builder::default()
|
Builder::default()
|
||||||
.targets([
|
.targets([
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ use log::{debug, info};
|
|||||||
use r2d2::Pool;
|
use r2d2::Pool;
|
||||||
use r2d2_sqlite::SqliteConnectionManager;
|
use r2d2_sqlite::SqliteConnectionManager;
|
||||||
use rusqlite::{OptionalExtension, params};
|
use rusqlite::{OptionalExtension, params};
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
static BLOB_MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/blob_migrations");
|
static BLOB_MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/blob_migrations");
|
||||||
|
|
||||||
@@ -25,23 +24,21 @@ impl BodyChunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Manages the blob database connection pool.
|
/// 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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct BlobManager {
|
pub struct BlobManager {
|
||||||
pool: Arc<Mutex<Pool<SqliteConnectionManager>>>,
|
pool: Pool<SqliteConnectionManager>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BlobManager {
|
impl BlobManager {
|
||||||
pub fn new(pool: Pool<SqliteConnectionManager>) -> Self {
|
pub fn new(pool: Pool<SqliteConnectionManager>) -> Self {
|
||||||
Self { pool: Arc::new(Mutex::new(pool)) }
|
Self { pool }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn connect(&self) -> BlobContext {
|
pub fn connect(&self) -> BlobContext {
|
||||||
let conn = self
|
let conn = self.pool.get().expect("Failed to get blob DB connection from pool");
|
||||||
.pool
|
|
||||||
.lock()
|
|
||||||
.expect("Failed to gain lock on blob DB")
|
|
||||||
.get()
|
|
||||||
.expect("Failed to get blob DB connection from pool");
|
|
||||||
BlobContext { conn }
|
BlobContext { conn }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,11 +54,15 @@ pub fn init_standalone(
|
|||||||
create_dir_all(parent)?;
|
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:?}");
|
info!("Initializing app database {db_path:?}");
|
||||||
let manager = sqlite_file_manager(db_path);
|
let manager = sqlite_file_manager(db_path);
|
||||||
let pool = Pool::builder()
|
let pool = Pool::builder()
|
||||||
.max_size(100)
|
.max_size(20)
|
||||||
|
.min_idle(Some(2))
|
||||||
.connection_timeout(Duration::from_secs(10))
|
.connection_timeout(Duration::from_secs(10))
|
||||||
.build(manager)
|
.build(manager)
|
||||||
.map_err(|e| Error::Database(e.to_string()))?;
|
.map_err(|e| Error::Database(e.to_string()))?;
|
||||||
@@ -70,7 +74,8 @@ pub fn init_standalone(
|
|||||||
// Blob database pool
|
// Blob database pool
|
||||||
let blob_manager = sqlite_file_manager(blob_path);
|
let blob_manager = sqlite_file_manager(blob_path);
|
||||||
let blob_pool = Pool::builder()
|
let blob_pool = Pool::builder()
|
||||||
.max_size(50)
|
.max_size(10)
|
||||||
|
.min_idle(Some(1))
|
||||||
.connection_timeout(Duration::from_secs(10))
|
.connection_timeout(Duration::from_secs(10))
|
||||||
.build(blob_manager)
|
.build(blob_manager)
|
||||||
.map_err(|e| Error::Database(e.to_string()))?;
|
.map_err(|e| Error::Database(e.to_string()))?;
|
||||||
|
|||||||
@@ -4,27 +4,25 @@ use crate::util::ModelPayload;
|
|||||||
use r2d2::Pool;
|
use r2d2::Pool;
|
||||||
use r2d2_sqlite::SqliteConnectionManager;
|
use r2d2_sqlite::SqliteConnectionManager;
|
||||||
use rusqlite::TransactionBehavior;
|
use rusqlite::TransactionBehavior;
|
||||||
use std::sync::{Arc, Mutex, mpsc};
|
use std::sync::mpsc;
|
||||||
use yaak_database::{ConnectionOrTx, DbContext};
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct QueryManager {
|
pub struct QueryManager {
|
||||||
pool: Arc<Mutex<Pool<SqliteConnectionManager>>>,
|
pool: Pool<SqliteConnectionManager>,
|
||||||
events_tx: mpsc::Sender<ModelPayload>,
|
events_tx: mpsc::Sender<ModelPayload>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QueryManager {
|
impl QueryManager {
|
||||||
pub fn new(pool: Pool<SqliteConnectionManager>, events_tx: mpsc::Sender<ModelPayload>) -> Self {
|
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<'_> {
|
pub fn connect(&self) -> ClientDb<'_> {
|
||||||
let conn = self
|
let conn = self.pool.get().expect("Failed to get a new DB connection from the pool");
|
||||||
.pool
|
|
||||||
.lock()
|
|
||||||
.expect("Failed to gain lock on DB")
|
|
||||||
.get()
|
|
||||||
.expect("Failed to get a new DB connection from the pool");
|
|
||||||
let ctx = DbContext::new(ConnectionOrTx::Connection(conn));
|
let ctx = DbContext::new(ConnectionOrTx::Connection(conn));
|
||||||
ClientDb::new(ctx, self.events_tx.clone())
|
ClientDb::new(ctx, self.events_tx.clone())
|
||||||
}
|
}
|
||||||
@@ -33,12 +31,7 @@ impl QueryManager {
|
|||||||
where
|
where
|
||||||
F: FnOnce(&ClientDb) -> T,
|
F: FnOnce(&ClientDb) -> T,
|
||||||
{
|
{
|
||||||
let conn = self
|
let conn = self.pool.get().expect("Failed to get new DB connection from the pool");
|
||||||
.pool
|
|
||||||
.lock()
|
|
||||||
.expect("Failed to gain lock on DB for transaction")
|
|
||||||
.get()
|
|
||||||
.expect("Failed to get new DB connection from the pool");
|
|
||||||
|
|
||||||
let ctx = DbContext::new(ConnectionOrTx::Connection(conn));
|
let ctx = DbContext::new(ConnectionOrTx::Connection(conn));
|
||||||
let db = ClientDb::new(ctx, self.events_tx.clone());
|
let db = ClientDb::new(ctx, self.events_tx.clone());
|
||||||
@@ -53,12 +46,7 @@ impl QueryManager {
|
|||||||
where
|
where
|
||||||
E: From<crate::error::Error>,
|
E: From<crate::error::Error>,
|
||||||
{
|
{
|
||||||
let mut conn = self
|
let mut conn = self.pool.get().expect("Failed to get new DB connection from the pool");
|
||||||
.pool
|
|
||||||
.lock()
|
|
||||||
.expect("Failed to gain lock on DB for transaction")
|
|
||||||
.get()
|
|
||||||
.expect("Failed to get new DB connection from the pool");
|
|
||||||
let tx = conn
|
let tx = conn
|
||||||
.transaction_with_behavior(TransactionBehavior::Immediate)
|
.transaction_with_behavior(TransactionBehavior::Immediate)
|
||||||
.expect("Failed to start DB transaction");
|
.expect("Failed to start DB transaction");
|
||||||
|
|||||||
Reference in New Issue
Block a user