diff --git a/sqlx-sqlite/src/connection/establish.rs b/sqlx-sqlite/src/connection/establish.rs index d811275409..069547c0c7 100644 --- a/sqlx-sqlite/src/connection/establish.rs +++ b/sqlx-sqlite/src/connection/establish.rs @@ -29,6 +29,7 @@ pub struct EstablishParams { busy_timeout: Duration, statement_cache_capacity: usize, log_settings: LogSettings, + pub(crate) thread_stack_size: Option, #[cfg(feature = "load-extension")] extensions: IndexMap>, pub(crate) thread_name: String, @@ -142,6 +143,7 @@ impl EstablishParams { busy_timeout: options.busy_timeout, statement_cache_capacity: options.statement_cache_capacity, log_settings: options.log_settings.clone(), + thread_stack_size: options.thread_stack_size, #[cfg(feature = "load-extension")] extensions, thread_name: (options.thread_name)(thread_id as u64), diff --git a/sqlx-sqlite/src/connection/worker.rs b/sqlx-sqlite/src/connection/worker.rs index 11c8778cc9..1e9a98d225 100644 --- a/sqlx-sqlite/src/connection/worker.rs +++ b/sqlx-sqlite/src/connection/worker.rs @@ -106,9 +106,14 @@ impl ConnectionWorker { pub(crate) async fn establish(params: EstablishParams) -> Result { let (establish_tx, establish_rx) = oneshot::channel(); - thread::Builder::new() - .name(params.thread_name.clone()) - .spawn(move || { + let mut builder = thread::Builder::new().name(params.thread_name.clone()); + + // Only set a custom stack size if explicitly configured + if let Some(stack_size) = params.thread_stack_size { + builder = builder.stack_size(stack_size); + } + + builder.spawn(move || { let (command_tx, command_rx) = flume::bounded(params.command_channel_size); let conn = match params.establish() { diff --git a/sqlx-sqlite/src/options/mod.rs b/sqlx-sqlite/src/options/mod.rs index b2849f243c..ffe871eeea 100644 --- a/sqlx-sqlite/src/options/mod.rs +++ b/sqlx-sqlite/src/options/mod.rs @@ -70,6 +70,7 @@ pub struct SqliteConnectOptions { pub(crate) log_settings: LogSettings, pub(crate) immutable: bool, pub(crate) vfs: Option>, + pub(crate) thread_stack_size: Option, pub(crate) pragmas: IndexMap, Option>>, @@ -204,6 +205,7 @@ impl SqliteConnectOptions { log_settings: Default::default(), immutable: false, vfs: None, + thread_stack_size: None, pragmas, #[cfg(feature = "load-extension")] extensions: Default::default(), @@ -233,6 +235,42 @@ impl SqliteConnectOptions { &self.filename } + /// Set the thread stack size in bytes for the SQLite worker thread. + /// + /// **This is an advanced option.** By default (`None`), SQLx uses the Rust standard library's + /// default stack size (typically 2 MB), which is safe for most use cases including user-supplied + /// callbacks and platform-specific requirements. + /// + /// Only set this if you have a specific reason to do so, such as running in an embedded environment + /// with constrained memory. Be aware that: + /// - User-supplied callbacks (hooks, custom functions) run on this thread and may have unpredictable + /// stack requirements + /// - Different platforms (32-bit vs 64-bit) have different stack size requirements + /// - Setting this too low may cause stack overflow crashes + /// + /// # Example + /// + /// ```rust,no_run + /// # use sqlx_sqlite::SqliteConnectOptions; + /// # use std::str::FromStr; + /// # fn example() -> Result<(), Box> { + /// let options = SqliteConnectOptions::from_str("sqlite::memory:")? + /// .thread_stack_size(1024 * 1024); // 1 MB - use with caution! + /// # Ok(()) + /// # } + /// ``` + pub fn thread_stack_size(mut self, size: usize) -> Self { + self.thread_stack_size = Some(size); + self + } + + /// Get the current thread stack size in bytes. + /// + /// Returns `None` if using the default stack size from the Rust standard library. + pub fn get_thread_stack_size(&self) -> Option { + self.thread_stack_size + } + /// Set the enforcement of [foreign key constraints](https://www.sqlite.org/pragma.html#pragma_foreign_keys). /// /// SQLx chooses to enable this by default so that foreign keys function as expected,