Skip to content
Open

Dev #11

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions apps/omninova-tauri/src-tauri/build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
fn main() {
tauri_build::build();

// Windows: raise the executable's default thread stack reserve to 8 MiB.
//
// The `Config` struct is large and deeply nested, so serde (de)serialization
// to/from JSON/TOML is stack-hungry. Tauri deserializes command arguments and
// serializes results on its IPC/main threads — created with the OS default
// stack size (PE `SizeOfStackReserve`, ~1 MiB by default on MSVC). That caused
// a `0xC00000FD` (STATUS_STACK_OVERFLOW) crash when saving the model config.
// Threads created with stack size 0 inherit this reserve, so this covers the
// main thread, Tauri/wry internal threads and the IPC response path.
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
if target_os == "windows" && target_env == "msvc" {
println!("cargo:rustc-link-arg-bins=/STACK:8388608");
}

let manifest_dir = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
let workspace_root = manifest_dir.join("../../..");
let profile = std::env::var("PROFILE").unwrap_or_else(|_| "debug".into());
Expand Down
35 changes: 28 additions & 7 deletions apps/omninova-tauri/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,9 +337,7 @@ async fn save_setup_config(
let mut next = setup_config_to_core(current, config)?;
let next_gateway_url = format!("http://{}:{}", next.gateway.host, next.gateway.port);

if let Err(error) = save_config_with_fallback(&mut next) {
eprintln!("[config warning] {error}");
}
save_config_with_fallback(&mut next)?;
runtime.set_config(next).await.map_err(|e| e.to_string())?;

if current_gateway_url != next_gateway_url {
Expand Down Expand Up @@ -1095,15 +1093,19 @@ fn normalize_optional_string(value: Option<String>) -> Option<String> {
})
}

fn user_home_dir() -> Option<PathBuf> {
std::env::var_os("HOME")
.map(PathBuf::from)
.or_else(|| std::env::var_os("USERPROFILE").map(PathBuf::from))
}

fn expand_tilde_path(value: &str) -> PathBuf {
if value == "~" {
return std::env::var_os("HOME")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(value));
return user_home_dir().unwrap_or_else(|| PathBuf::from(value));
}

if let Some(rest) = value.strip_prefix("~/") {
if let Some(home) = std::env::var_os("HOME").map(PathBuf::from) {
if let Some(home) = user_home_dir() {
return home.join(rest);
}
}
Expand Down Expand Up @@ -1167,8 +1169,27 @@ fn display_provider_name(id: &str) -> String {
}
}

/// Worker-thread stack size for the async runtime that backs Tauri commands.
///
/// The `Config` struct is large (~8.7 KiB) and very deeply nested, so serde
/// (de)serialization to/from TOML/JSON consumes a lot of stack. On Windows the
/// default worker-thread stack (~1 MiB) overflows during config save, crashing
/// the process with `0xC00000FD` (STATUS_STACK_OVERFLOW). An 8 MiB stack keeps
/// every command handler (which may (de)serialize `Config`) well within bounds.
const ASYNC_RUNTIME_STACK_BYTES: usize = 8 * 1024 * 1024;

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
// Install a tokio runtime with larger worker stacks *before* anything uses
// the async runtime, so all Tauri command handlers run with enough stack.
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.thread_stack_size(ASYNC_RUNTIME_STACK_BYTES)
.build()
.expect("Failed to build async runtime");
let runtime: &'static tokio::runtime::Runtime = Box::leak(Box::new(runtime));
tauri::async_runtime::set(runtime.handle().clone());

omninova_core::init().expect("Failed to initialize core");

let config = Config::load_or_init().expect("Failed to load config");
Expand Down
25 changes: 22 additions & 3 deletions crates/omninova-core/src/config/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ use tracing::info;
const APP_DIR_NAME: &str = ".omninova";
const CONFIG_FILE_NAME: &str = "config.toml";
const ACTIVE_WORKSPACE_FILE: &str = "active_workspace.toml";
/// Windows async/Tauri worker threads often use ~1 MiB stacks; Config TOML
/// serialization is deeply nested and can overflow without a larger stack.
const CONFIG_SAVE_STACK_BYTES: usize = 8 * 1024 * 1024;

/// Resolve the config directory with the following priority:
/// 1. `OMNINOVA_CONFIG_DIR` env var
Expand Down Expand Up @@ -107,6 +110,19 @@ impl Config {

/// Save the current config to disk as TOML.
pub fn save(&self) -> Result<()> {
if cfg!(target_os = "windows") {
let cfg = self.clone();
return std::thread::Builder::new()
.stack_size(CONFIG_SAVE_STACK_BYTES)
.spawn(move || cfg.save_inner())
.map_err(|e| anyhow::anyhow!("failed to spawn config save thread: {e}"))?
.join()
.map_err(|_| anyhow::anyhow!("config save thread panicked"))?;
}
self.save_inner()
}

fn save_inner(&self) -> Result<()> {
let content = toml::to_string_pretty(self)
.context("Failed to serialize config to TOML")?;

Expand Down Expand Up @@ -143,11 +159,14 @@ impl Config {
.config_path
.parent()
.unwrap_or(&self.config_path)
.to_string_lossy();
.to_string_lossy()
.to_string();

let mut table = toml::Table::new();
table.insert("config_dir".to_string(), toml::Value::String(dir_str));
let body = toml::to_string(&table).context("Failed to serialize active workspace pointer")?;
let content = format!(
"# Auto-generated – points to the active OmniNova workspace\nconfig_dir = \"{}\"\n",
dir_str
"# Auto-generated – points to the active OmniNova workspace\n{body}\n"
);
std::fs::write(&active_path, content)?;
Ok(())
Expand Down
Loading