mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-25 10:37:11 -06:00
A render still running on a worker thread while the process exits crashes in the driver (SIGSEGV in the NVIDIA Vulkan driver on Linux), where neither the panic hook nor catch_unwind helps: pool workers were detached, the devices are statics that are never dropped, and nothing on the way out waited for GPU work. - gpu: every entry into a device (render, device creation, pool trim, denoise runner and tiles) is counted as work in flight (exit.rs). begin_shutdown() lets nothing new in; wait_idle(timeout) waits for what is inside. - preview: JobPool keeps its workers. shutdown(timeout) and Drop discard the queued jobs and wait, bounded, for the running ones (2 s on drop); never for the calling thread, and not twice. - engine: a render job caught by the shutdown gives up instead of rendering on the CPU, so quitting doesn't wait for it. - app: on_exit ends with LightcraftApp::shutdown (2 s for everything), after settings and library are saved; the macOS menu-bar Quit goes through on_exit. Not changed: the export, merge, denoise and import threads are still detached; their GPU work goes through the gate, their CPU work is cut off at exit as before. Tests: pool drop waits / is bounded / survives a panicked job and its own job dropping it; the gate; GPU renders refused and the device not created after begin_shutdown; the app's shutdown leaves no render worker. The crash itself needs Linux + NVIDIA and was not reproduced here (Windows, DX12): 9 of 9 quits of the app mid-render exit with code 0 in under 0.8 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
41 lines
2.0 KiB
Rust
41 lines
2.0 KiB
Rust
//! Issue #620: quitting while previews render must not leave a render worker running (on Linux
|
|
//! with the NVIDIA driver, one inside the GPU when the process exits is a SIGSEGV).
|
|
//! `LightcraftApp::shutdown` stops the render workers and closes the GPU for the whole process,
|
|
//! hence one test in a binary of its own.
|
|
|
|
use std::time::{Duration, Instant};
|
|
|
|
use lightcraft_ui_egui::headless::Headless;
|
|
use lightcraft_ui_egui::{LightcraftApp, Services};
|
|
|
|
#[test]
|
|
fn shutdown_leaves_no_render_worker_running() {
|
|
let services = Services { png: None, ..Default::default() };
|
|
let mut h = Headless::new(LightcraftApp::new(lightcraft_engine::Session::with_demo(), services), [1200.0, 760.0], 1.0);
|
|
// frames until the grid's thumbnails are being rendered on the workers
|
|
let t0 = Instant::now();
|
|
while h.app.renderer.live_workers() == 0 || h.app.renderer.in_flight() == 0 {
|
|
assert!(t0.elapsed() < Duration::from_secs(120), "no render started");
|
|
h.step();
|
|
}
|
|
assert!(!lightcraft_engine::gpu::shutting_down());
|
|
|
|
assert!(h.app.shutdown(Duration::from_secs(120)), "the jobs that were running ended in time");
|
|
assert_eq!(h.app.renderer.live_workers(), 0);
|
|
assert_eq!(h.app.renderer.queued(), 0, "jobs that had not started are dropped");
|
|
assert!(lightcraft_engine::gpu::shutting_down());
|
|
assert!(lightcraft_engine::gpu::wait_idle(Duration::ZERO), "nothing is inside the GPU");
|
|
|
|
// frames after it (the window's last ones) ask for renders that are not started any more
|
|
for _ in 0..3 {
|
|
h.step();
|
|
}
|
|
assert_eq!(h.app.renderer.live_workers(), 0);
|
|
assert_eq!(h.app.renderer.queued(), 0);
|
|
|
|
// a job that was caught before its render gives up: quitting doesn't wait for a CPU render
|
|
let id = h.app.session.active().or_else(|| h.app.session.catalog.photos().next().map(|p| p.id)).expect("a demo photo");
|
|
let job = h.app.session.render_job(id, 300, 200, false, true).expect("a render job");
|
|
assert_eq!(job.run().rendered.err().as_deref(), Some("LightCraft is closing"));
|
|
}
|