diff --git a/README.md b/README.md index d6de79c..c21a552 100644 --- a/README.md +++ b/README.md @@ -124,3 +124,8 @@ The worker delays its cuBLASLt import and checks runtime library loading before CUDA calls. No global PATH changes or extra DLL search directories are needed. Release-matrix publication remains separate from this manual gate artifact. + +This delay-loading behavior is Windows-only. The Linux ELF worker retains a +`DT_NEEDED` dependency on `libcublasLt.so.12`; without that runtime on the +library search path, even `--probe-floor` exits 127 before reaching the +driver-only probe. diff --git a/crates/synapse-module/src/lib.rs b/crates/synapse-module/src/lib.rs index fb3641c..284647c 100644 --- a/crates/synapse-module/src/lib.rs +++ b/crates/synapse-module/src/lib.rs @@ -6082,9 +6082,10 @@ struct OwnedCudaFloorReading { compute_minor: u32, } -static OWNED_CUDA_PROBE: std::sync::LazyLock< - Mutex>>, -> = std::sync::LazyLock::new(|| Mutex::new(HashMap::new())); +type OwnedCudaProbeEntry = Arc>>; + +static OWNED_CUDA_PROBE: std::sync::LazyLock>> = + std::sync::LazyLock::new(|| Mutex::new(HashMap::new())); /// Cache successes and failures per worker; a complete environment override skips it. fn owned_cuda_probe_floor(worker: Option<&Path>) -> Result { @@ -6094,14 +6095,17 @@ fn owned_cuda_probe_floor(worker: Option<&Path>) -> Result\"{}\"\r\n:wait\r\nif exist \"{}\" goto done\r\nping -n 2 127.0.0.1 >nul\r\ngoto wait\r\n:done\r\necho {json}\r\n", + ready.display(), release.display() + ) + } else { + format!( + "#!/bin/sh\nprintf ready >'{}'\nwhile [ ! -f '{}' ]; do sleep 0.05; done\necho '{json}'\n", + ready.display(), release.display() + ) + }; + let success = if cfg!(windows) { + format!("@echo off\r\necho {json}\r\n") + } else { + format!("#!/bin/sh\necho '{json}'\n") + }; + fs::write(&slow, stalled).unwrap(); + fs::write(&quick, success).unwrap(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + for path in [&slow, &quick] { + fs::set_permissions(path, fs::Permissions::from_mode(0o700)).unwrap(); + } + } + let stalled = std::thread::spawn(move || owned_cuda_probe_floor(Some(&slow))); + let ready_deadline = Instant::now() + Duration::from_secs(3); + while !ready.exists() && Instant::now() < ready_deadline { + std::thread::sleep(Duration::from_millis(10)); + } + let confirmed_ready = ready.exists(); + let (tx, rx) = std::sync::mpsc::channel(); + let other = std::thread::spawn(move || { + let _ = tx.send(owned_cuda_probe_floor(Some(&quick))); + }); + // The quick probe must finish while the first worker is still blocked. + let result = rx.recv_timeout(Duration::from_secs(3)); + fs::write(&release, "release").unwrap(); + let stalled_result = stalled.join().unwrap(); + other.join().unwrap(); + assert!(confirmed_ready, "stalled worker did not signal readiness"); + assert_eq!(stalled_result.unwrap().driver_api, 13030); + assert_eq!( + result + .expect("unrelated probe blocked behind stalled worker") + .unwrap() + .driver_api, + 13030 + ); + fs::remove_dir_all(root).unwrap(); + } + #[test] #[ignore = "requires a staged CUDA worker and supported GPU; run explicitly"] fn cuda_floor_probe_matches_real_worker_binary_output() {