From 2b8d610f9759eec60669a811c8624b47977c0786 Mon Sep 17 00:00:00 2001 From: Stuart Cameron Date: Sun, 14 Jun 2026 21:22:39 +1000 Subject: [PATCH] worker: write VapourSynth autoload conf per-process to fix a plugin-load race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build_environment() wrote vapoursynth.auto.conf to a shared, fixed-name path on every invocation. `flutter test` runs test files concurrently, so overlapping worker/vspipe processes could read the conf mid-truncation — UserPluginDir came back empty and plugin autoload silently skipped every plugin, surfacing as an intermittent "No attribute with the name fmtc exists" (nightly macOS arm64). Write the conf to a per-process path (named by PID) in the temp dir instead: each process writes its own conf fully before spawning vspipe, and no other process touches it. The .vpy scripts were already unique per job (UUID); the conf was the one shared file. Reproduced and verified: a shared conf failed ~1/120 concurrent runs with the exact error; the per-process conf was 0/360. Co-Authored-By: Claude Opus 4.8 (1M context) --- worker/src/dependency_locator.rs | 40 +++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/worker/src/dependency_locator.rs b/worker/src/dependency_locator.rs index 09863b9..1640188 100644 --- a/worker/src/dependency_locator.rs +++ b/worker/src/dependency_locator.rs @@ -626,6 +626,26 @@ impl DependencyLocator { } } + /// Write the VapourSynth autoload config to a PER-PROCESS path and return it. + /// + /// `build_environment` runs on every worker invocation, and `flutter test` + /// runs test files concurrently, so writing a shared, fixed-name conf let an + /// overlapping vspipe read it mid-truncation — `UserPluginDir` came back + /// empty and plugin autoload silently skipped everything (the intermittent + /// "No attribute with the name fmtc exists" on the nightly). A per-process + /// file (named by PID) is written fully by this process and touched by no + /// other, eliminating the race. Verified by reproduction: a shared conf + /// failed ~1/120 concurrent runs; per-process was 0/360. + fn write_runtime_conf(plugins_dir: &Path) -> Option { + let content = format!( + "UserPluginDir={}\nAutoloadUserPluginDir=true\nAutoloadSystemPluginDir=false\n", + plugins_dir.to_string_lossy() + ); + let conf_path = env::temp_dir().join(format!("vapourbox-vs-{}.conf", std::process::id())); + std::fs::write(&conf_path, content).ok()?; + Some(conf_path) + } + /// Build environment variables for running vspipe/ffmpeg. pub fn build_environment(&self) -> std::collections::HashMap { let mut env = std::collections::HashMap::new(); @@ -679,13 +699,9 @@ impl DependencyLocator { // This replaces the config generation in the vspipe wrapper script, // allowing us to call vspipe-bin directly (so kill() works). let plugins_dir = vs_lib_path.join("plugins"); - let conf_path = vs_lib_path.join("vapoursynth.auto.conf"); - let conf_content = format!( - "UserPluginDir={}\nAutoloadUserPluginDir=true\nAutoloadSystemPluginDir=false\n", - plugins_dir.to_string_lossy() - ); - let _ = std::fs::write(&conf_path, &conf_content); - env.insert("VAPOURSYNTH_CONF_PATH".to_string(), conf_path.to_string_lossy().to_string()); + if let Some(conf_path) = Self::write_runtime_conf(&plugins_dir) { + env.insert("VAPOURSYNTH_CONF_PATH".to_string(), conf_path.to_string_lossy().to_string()); + } } #[cfg(target_os = "linux")] @@ -715,13 +731,9 @@ impl DependencyLocator { // Generate VapourSynth config (same as macOS) let plugins_dir = vs_lib_path.join("plugins"); - let conf_path = vs_lib_path.join("vapoursynth.auto.conf"); - let conf_content = format!( - "UserPluginDir={}\nAutoloadUserPluginDir=true\nAutoloadSystemPluginDir=false\n", - plugins_dir.to_string_lossy() - ); - let _ = std::fs::write(&conf_path, &conf_content); - env.insert("VAPOURSYNTH_CONF_PATH".to_string(), conf_path.to_string_lossy().to_string()); + if let Some(conf_path) = Self::write_runtime_conf(&plugins_dir) { + env.insert("VAPOURSYNTH_CONF_PATH".to_string(), conf_path.to_string_lossy().to_string()); + } } env