fix: hard-stop studio preview to avoid teardown corruption
The disposable preview process was tripping MangoHud's shutdown path and spamming allocator corruption messages on stop/restart. Use an immediate kill for Studio preview teardown and give the preview child a cleaner socket thread shutdown path so the live preview controls stay fast and quiet.
This commit is contained in:
@@ -115,6 +115,15 @@ impl Default for SimState {
|
||||
.and_then(|value| value.parse::<u32>().ok())
|
||||
.unwrap_or(120);
|
||||
let present_mode = PreviewPresentMode::from_env();
|
||||
let paused = std::env::var("MANGOTUNE_PREVIEW_PAUSED")
|
||||
.ok()
|
||||
.map(|value| {
|
||||
matches!(
|
||||
value.trim().to_ascii_lowercase().as_str(),
|
||||
"1" | "true" | "yes" | "on"
|
||||
)
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
Self {
|
||||
gpu_load_target,
|
||||
@@ -125,7 +134,7 @@ impl Default for SimState {
|
||||
gpu_passes,
|
||||
interaction_steps,
|
||||
present_mode,
|
||||
paused: false,
|
||||
paused,
|
||||
should_quit: false,
|
||||
scene_preset,
|
||||
}
|
||||
@@ -152,11 +161,18 @@ fn main() -> Result<()> {
|
||||
|
||||
let state = Arc::new(Mutex::new(SimState::default()));
|
||||
let state_for_socket = Arc::clone(&state);
|
||||
std::thread::spawn(move || {
|
||||
let socket_thread = std::thread::spawn(move || {
|
||||
let rt = tokio::runtime::Runtime::new().expect("tokio runtime");
|
||||
rt.block_on(socket_api::run(state_for_socket))
|
||||
.expect("socket API crashed");
|
||||
});
|
||||
|
||||
renderer::run(state)
|
||||
let result = renderer::run(Arc::clone(&state));
|
||||
|
||||
if let Ok(mut sim) = state.lock() {
|
||||
sim.should_quit = true;
|
||||
}
|
||||
|
||||
let _ = socket_thread.join();
|
||||
result
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::net::UnixListener;
|
||||
use tokio::time::{timeout, Duration};
|
||||
|
||||
pub fn socket_path() -> String {
|
||||
std::env::var("MANGOTUNE_SOCKET").unwrap_or_else(|_| "/tmp/mangotune_preview.sock".into())
|
||||
@@ -71,8 +72,12 @@ pub async fn run(state: Arc<Mutex<SimState>>) -> Result<()> {
|
||||
log::info!("Socket API listening on {}", path);
|
||||
|
||||
loop {
|
||||
match listener.accept().await {
|
||||
Ok((stream, _addr)) => {
|
||||
if should_quit(&state) {
|
||||
break;
|
||||
}
|
||||
|
||||
match timeout(Duration::from_millis(100), listener.accept()).await {
|
||||
Ok(Ok((stream, _addr))) => {
|
||||
let state = Arc::clone(&state);
|
||||
tokio::spawn(async move {
|
||||
if let Err(err) = handle_connection(stream, state).await {
|
||||
@@ -80,11 +85,19 @@ pub async fn run(state: Arc<Mutex<SimState>>) -> Result<()> {
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
Ok(Err(err)) => {
|
||||
log::error!("Socket accept error: {}", err);
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = tokio::fs::remove_file(&path).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn should_quit(state: &Arc<Mutex<SimState>>) -> bool {
|
||||
state.lock().map(|sim| sim.should_quit).unwrap_or(true)
|
||||
}
|
||||
|
||||
async fn handle_connection(
|
||||
@@ -109,6 +122,10 @@ async fn handle_connection(
|
||||
let mut bytes = serde_json::to_vec(&response)?;
|
||||
bytes.push(b'\n');
|
||||
write_half.write_all(&bytes).await?;
|
||||
|
||||
if should_quit(&state) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -87,6 +87,16 @@ impl Runner {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Kill a running process immediately (SIGKILL, then wait).
|
||||
pub fn kill(mut process: RunningProcess) -> Result<()> {
|
||||
let _ = kill(Pid::from_raw(process.child.id() as i32), Signal::SIGKILL);
|
||||
process
|
||||
.child
|
||||
.wait()
|
||||
.map(|_| ())
|
||||
.map_err(|err| anyhow!("failed waiting for process exit: {err}"))
|
||||
}
|
||||
|
||||
/// Send SIGUSR1 to reload config in a running MangoHud process.
|
||||
pub fn reload_config(pid: u32) -> Result<()> {
|
||||
kill(Pid::from_raw(pid as i32), Signal::SIGUSR1)?;
|
||||
|
||||
+27
-12
@@ -150,6 +150,10 @@ impl PreviewScene {
|
||||
"MANGOTUNE_PREVIEW_VSYNC".to_string(),
|
||||
if studio.vsync { "1" } else { "0" }.to_string(),
|
||||
),
|
||||
(
|
||||
"MANGOTUNE_PREVIEW_PAUSED".to_string(),
|
||||
if studio.paused { "1" } else { "0" }.to_string(),
|
||||
),
|
||||
],
|
||||
})
|
||||
}
|
||||
@@ -379,13 +383,7 @@ impl PreviewController {
|
||||
};
|
||||
|
||||
if let Some(running) = running {
|
||||
if running.request.scene == PreviewScene::Studio {
|
||||
let _ = send_studio_command(&socket_path, r#"{"cmd":"quit"}"#);
|
||||
std::thread::sleep(Duration::from_millis(120));
|
||||
}
|
||||
Runner::stop(running.process)?;
|
||||
let _ = fs::remove_file(socket_path);
|
||||
let _ = fs::remove_file(temp_config_path);
|
||||
stop_running_preview(running, &socket_path, &temp_config_path)?;
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
@@ -461,10 +459,6 @@ impl PreviewController {
|
||||
state.current.take()
|
||||
};
|
||||
|
||||
if let Some(running) = running {
|
||||
let _ = Runner::stop(running.process);
|
||||
}
|
||||
|
||||
let (temp_path, socket_path) = {
|
||||
let Ok(state) = self.inner.lock() else {
|
||||
return Err(anyhow!("could not access preview state"));
|
||||
@@ -475,6 +469,10 @@ impl PreviewController {
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(running) = running {
|
||||
let _ = stop_running_preview(running, &socket_path, &temp_path);
|
||||
}
|
||||
|
||||
let layout_width = request.layout_width_override.unwrap_or(request.width);
|
||||
write_preview_config(config, &temp_path, layout_width, request.scene)?;
|
||||
if request.scene == PreviewScene::Studio {
|
||||
@@ -503,7 +501,6 @@ impl PreviewController {
|
||||
if request.scene == PreviewScene::Studio {
|
||||
wait_for_studio_socket(&socket_path, Duration::from_secs(3))
|
||||
.context("studio preview socket did not appear")?;
|
||||
apply_studio_runtime_settings(&socket_path, &request.studio)?;
|
||||
}
|
||||
|
||||
let Ok(mut state) = self.inner.lock() else {
|
||||
@@ -516,6 +513,24 @@ impl PreviewController {
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_running_preview(
|
||||
running: RunningPreview,
|
||||
socket_path: &Path,
|
||||
temp_config_path: &Path,
|
||||
) -> Result<()> {
|
||||
if running.request.scene == PreviewScene::Studio {
|
||||
Runner::kill(running.process)?;
|
||||
let _ = fs::remove_file(socket_path);
|
||||
let _ = fs::remove_file(temp_config_path);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Runner::stop(running.process)?;
|
||||
let _ = fs::remove_file(socket_path);
|
||||
let _ = fs::remove_file(temp_config_path);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_preview_config(
|
||||
config: &AnnotatedConfig,
|
||||
path: &PathBuf,
|
||||
|
||||
Reference in New Issue
Block a user