Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-map/tests/common/one_gpu.rs

//! **ONE GPU BRING-UP AT A TIME — and the turn itself now lives in `facett-core`.**
//!
//! `egui_kittest::Harness::render()` brings up its own wgpu device, and two `#[test]`
//! threads doing that at once SIGSEGVs on this host. MEASURED 2026-08-27, both files that
//! do a pixel census:
//!
//! ```text
//!   rail_tier_pixels        default runner → signal: 11, SIGSEGV   --test-threads=1 → 2 passed
//!   place_label_tier_pixels default runner → signal: 11, SIGSEGV   --test-threads=1 → 2 passed
//! ```
//!
//! `rail_tier_pixels`'s header documents the flag, which works for a hand run and does
//! nothing for a plain `cargo test` — where nobody passes it, and a SIGSEGV in one file
//! fails the whole suite for a reason that has nothing to do with the code under test.
//!
//! # Why this file is now four lines
//!
//! It used to declare the `static … Mutex<()>` itself, and to say in as many words that
//! *"separate test binaries are separate processes and do not contend"*. Both halves were
//! wrong by 2026-08-31:
//!
//! * the claim is false — `line_gpu` SIGSEGV'd beside `facett-geomap`'s device tests, in a
//!   different process, and passes 7/7 alone;
//! * a lock that lives in **one crate's test directory** cannot be taken by the crates that
//!   needed it most. `facett-core`'s own eight hand-rolled device probes had no turn at all
//!   and deadlocked `cargo test -p facett-core --lib` for **13 minutes** (20 threads in
//!   `futex_do_wait`, GPU at 0 %, 632 MiB held) in BOTH profiles on the same day, and
//!   `facett-map3d`'s `ssao_proof` took a `signal: 11` in a whole-workspace release run.
//!   The same 74 `render::gpu::*` tests pass in **5.2 s** under `--test-threads=1`.
//!
//! So the turn moved to [`facett_core::render::gputurn`], which is feature-free and
//! reachable from every facett crate's tests, and gained a cross-process `flock` half. This
//! file re-exports it. It is **not** a second lock: two mutexes over one device serialise
//! nothing and the second one reads as coverage, which is exactly how six copies of this
//! static coexisted while the crashes went on (see `one_gpu_turn_is_the_only_one.rs`).
//!
//! # Why a `#[path]` include and not `mod common;`
//!
//! Exactly [`heap.rs`](heap)'s reason: `common/mod.rs` is the wgpu DEVICE gate and pulls
//! `wgpu` in, and a file that only wants the turn must stay buildable on a default
//! (feature-less) build. `gputurn` is feature-free for the same reason.
//!
//! ```ignore
//! #[path = "common/one_gpu.rs"]
//! mod one_gpu;
//!
//! #[test]
//! fn something_that_renders() {
//!     let _gpu = one_gpu::hold();
//!     …
//! }
//! ```
#![allow(dead_code)]

/// Hold the render lane for the rest of the caller's scope — [`facett_core`]'s turn, not a
/// copy of it. See that module for the two halves (in-process `Mutex` + machine-wide
/// `flock`), the 180 s deadline that keeps a wedged holder from wedging the run, and
/// `$FACETT_GPU_LOCK`.
// Each test binary that mounts `common` uses a subset of this module (hence the
// `dead_code` allow above); a binary that only calls `hold` leaves `Turn` unused.
#[allow(unused_imports)]
pub use facett_core::render::gputurn::{hold, Turn};