fix(vulkan-smoke): harden timeout and ci closure

This commit is contained in:
2026-06-25 08:04:47 +04:00
parent 607a64ca8d
commit b617e2958d
4 changed files with 108 additions and 16 deletions
@@ -51,9 +51,10 @@ where
fn commit(mut self) -> T { fn commit(mut self) -> T {
self.rollback.take(); self.rollback.take();
self.value match self.value.take() {
.take() Some(value) => value,
.expect("rollback guard must hold a value until commit") None => unreachable!("rollback guard must hold a value until commit"),
}
} }
} }
@@ -259,10 +259,12 @@ fn create_pipeline_layout(
}) })
} }
#[allow(clippy::cast_precision_loss)]
fn extent_component_to_f32(value: u32) -> f32 { fn extent_component_to_f32(value: u32) -> f32 {
value as f32 value as f32
} }
#[allow(clippy::too_many_lines)]
fn create_graphics_pipeline( fn create_graphics_pipeline(
device: &VulkanLogicalDeviceProbe, device: &VulkanLogicalDeviceProbe,
render_pass: vk::RenderPass, render_pass: vk::RenderPass,
+10 -3
View File
@@ -19,19 +19,26 @@ fn main() {
println!("cargo:rustc-env=FPARKAN_BUILD_RUST_TOOLCHAIN={toolchain}"); println!("cargo:rustc-env=FPARKAN_BUILD_RUST_TOOLCHAIN={toolchain}");
} }
let workspace_root = if let Some(workspace_root) = workspace_root() {
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("manifest dir")).join("../..");
if let Some(git_dir) = git_dir(&workspace_root) { if let Some(git_dir) = git_dir(&workspace_root) {
emit_git_rerun_hints(&git_dir); emit_git_rerun_hints(&git_dir);
} }
if let Some(commit_sha) = env_commit_sha().or_else(|| git_head_commit_sha(&workspace_root)) { if let Some(commit_sha) = env_commit_sha().or_else(|| git_head_commit_sha(&workspace_root))
{
println!("cargo:rustc-env=FPARKAN_BUILD_COMMIT_SHA={commit_sha}"); println!("cargo:rustc-env=FPARKAN_BUILD_COMMIT_SHA={commit_sha}");
} }
if let Some(git_dirty) = git_dirty(&workspace_root) { if let Some(git_dirty) = git_dirty(&workspace_root) {
println!("cargo:rustc-env=FPARKAN_BUILD_GIT_DIRTY={git_dirty}"); println!("cargo:rustc-env=FPARKAN_BUILD_GIT_DIRTY={git_dirty}");
} }
} }
}
fn workspace_root() -> Option<PathBuf> {
env::var_os("CARGO_MANIFEST_DIR")
.map(PathBuf::from)
.map(|manifest_dir| manifest_dir.join("../.."))
}
fn env_commit_sha() -> Option<String> { fn env_commit_sha() -> Option<String> {
["GITHUB_SHA", "SOURCE_VERSION", "BUILD_VCS_NUMBER"] ["GITHUB_SHA", "SOURCE_VERSION", "BUILD_VCS_NUMBER"]
+85 -3
View File
@@ -18,11 +18,14 @@ use fparkan_render_vulkan::{
use serde::Serialize; use serde::Serialize;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
use winit::event::StartCause;
use winit::event::WindowEvent; use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::{Window, WindowId}; use winit::window::{Window, WindowId};
const SCHEMA_VERSION: &str = "fparkan-native-smoke-v1"; const SCHEMA_VERSION: &str = "fparkan-native-smoke-v1";
@@ -50,13 +53,37 @@ fn main() {
fn run(args: &[String]) -> Result<String, String> { fn run(args: &[String]) -> Result<String, String> {
let options = SmokeOptions::parse(args)?; let options = SmokeOptions::parse(args)?;
let event_loop = EventLoop::new().map_err(|err| format!("winit event loop: {err}"))?; let event_loop = EventLoop::new().map_err(|err| format!("winit event loop: {err}"))?;
let mut app = SmokeApp::new(options); event_loop.set_control_flow(ControlFlow::Poll);
let completed = Arc::new(AtomicBool::new(false));
spawn_timeout_watchdog(options.clone(), Arc::clone(&completed));
let mut app = SmokeApp::new(options, completed);
if let Err(err) = event_loop.run_app(&mut app) { if let Err(err) = event_loop.run_app(&mut app) {
app.error = Some(format!("winit event loop: {err}")); app.error = Some(format!("winit event loop: {err}"));
} }
app.finish() app.finish()
} }
fn spawn_timeout_watchdog(options: SmokeOptions, completed: Arc<AtomicBool>) {
std::thread::spawn(move || {
std::thread::sleep(Duration::from_secs(options.timeout_seconds));
if completed.load(Ordering::SeqCst) || options.out.exists() {
return;
}
let failure_reason = format!(
"native smoke timed out after {} seconds",
options.timeout_seconds
);
if let Ok(report) = render_timeout_failure_report(&options, &failure_reason) {
if let Some(parent) = options.out.parent() {
let _ = std::fs::create_dir_all(parent);
}
let _ = std::fs::write(&options.out, report);
}
eprintln!("{failure_reason}");
std::process::exit(2);
});
}
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
struct SmokeOptions { struct SmokeOptions {
out: PathBuf, out: PathBuf,
@@ -125,6 +152,7 @@ impl SmokeOptions {
struct SmokeApp { struct SmokeApp {
options: SmokeOptions, options: SmokeOptions,
completed: Arc<AtomicBool>,
window_id: Option<WindowId>, window_id: Option<WindowId>,
window: Option<Window>, window: Option<Window>,
renderer: Option<VulkanSmokeRenderer>, renderer: Option<VulkanSmokeRenderer>,
@@ -138,9 +166,10 @@ struct SmokeApp {
} }
impl SmokeApp { impl SmokeApp {
fn new(options: SmokeOptions) -> Self { fn new(options: SmokeOptions, completed: Arc<AtomicBool>) -> Self {
Self { Self {
options, options,
completed,
window_id: None, window_id: None,
window: None, window: None,
renderer: None, renderer: None,
@@ -158,6 +187,7 @@ impl SmokeApp {
if let Some(output) = self.output.take() { if let Some(output) = self.output.take() {
return Ok(output); return Ok(output);
} }
self.completed.store(true, Ordering::SeqCst);
let error = self let error = self
.error .error
.clone() .clone()
@@ -330,6 +360,7 @@ impl SmokeApp {
return; return;
} }
}; };
self.completed.store(true, Ordering::SeqCst);
if let Err(err) = self.write_report(&report) { if let Err(err) = self.write_report(&report) {
self.error = Some(err); self.error = Some(err);
event_loop.exit(); event_loop.exit();
@@ -352,7 +383,57 @@ impl SmokeApp {
} }
} }
fn render_timeout_failure_report(
options: &SmokeOptions,
failure_reason: &str,
) -> Result<String, String> {
let smoke_report = SmokeReport {
schema_version: SCHEMA_VERSION,
commit_sha: compiled_commit_sha(),
git_dirty: compiled_git_dirty(),
runner_identity: measured_runner_identity(),
runner_architecture: actual_architecture(),
rust_toolchain: compiled_rust_toolchain(),
target_triple: compiled_target_triple(),
platform: actual_platform(),
status: "failed",
failure_reason: Some(failure_reason),
frames: 0,
resize_count: 0,
swapchain_recreate_count: 0,
validation_warning_count: 0,
validation_error_count: 0,
validation_vuids: &[],
requested_frames: options.frames,
timeout_seconds: options.timeout_seconds,
shader_manifest_hash: "",
vulkan_loader_status: "failed",
vulkan_instance_status: "failed",
window_status: "failed",
vulkan_surface_status: "failed",
vulkan_device_status: "failed",
vulkan_device_name: "",
vulkan_logical_device_status: "failed",
vulkan_logical_device_graphics_queue_family: 0,
vulkan_logical_device_present_queue_family: 0,
vulkan_logical_device_enabled_extension_count: 0,
vulkan_swapchain_status: "failed",
vulkan_swapchain_width: 0,
vulkan_swapchain_height: 0,
vulkan_swapchain_image_count: 0,
vulkan_portability_enumeration: false,
vulkan_portability_subset_enabled: false,
};
serde_json::to_string_pretty(&smoke_report)
.map(|json| format!("{json}\n"))
.map_err(|err| format!("native smoke report serialization failed: {err}"))
}
impl ApplicationHandler for SmokeApp { impl ApplicationHandler for SmokeApp {
fn new_events(&mut self, event_loop: &ActiveEventLoop, _cause: StartCause) {
let _ = self.abort_if_timed_out(event_loop);
}
fn resumed(&mut self, event_loop: &ActiveEventLoop) { fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.abort_if_timed_out(event_loop) { if self.abort_if_timed_out(event_loop) {
return; return;
@@ -818,6 +899,7 @@ mod tests {
resize_frame: DEFAULT_RESIZE_FRAME, resize_frame: DEFAULT_RESIZE_FRAME,
timeout_seconds: 7, timeout_seconds: 7,
}, },
completed: Arc::new(AtomicBool::new(false)),
window_id: None, window_id: None,
window: None, window: None,
renderer: None, renderer: None,