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())
|
.and_then(|value| value.parse::<u32>().ok())
|
||||||
.unwrap_or(120);
|
.unwrap_or(120);
|
||||||
let present_mode = PreviewPresentMode::from_env();
|
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 {
|
Self {
|
||||||
gpu_load_target,
|
gpu_load_target,
|
||||||
@@ -125,7 +134,7 @@ impl Default for SimState {
|
|||||||
gpu_passes,
|
gpu_passes,
|
||||||
interaction_steps,
|
interaction_steps,
|
||||||
present_mode,
|
present_mode,
|
||||||
paused: false,
|
paused,
|
||||||
should_quit: false,
|
should_quit: false,
|
||||||
scene_preset,
|
scene_preset,
|
||||||
}
|
}
|
||||||
@@ -152,11 +161,18 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
let state = Arc::new(Mutex::new(SimState::default()));
|
let state = Arc::new(Mutex::new(SimState::default()));
|
||||||
let state_for_socket = Arc::clone(&state);
|
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");
|
let rt = tokio::runtime::Runtime::new().expect("tokio runtime");
|
||||||
rt.block_on(socket_api::run(state_for_socket))
|
rt.block_on(socket_api::run(state_for_socket))
|
||||||
.expect("socket API crashed");
|
.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 std::sync::{Arc, Mutex};
|
||||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||||
use tokio::net::UnixListener;
|
use tokio::net::UnixListener;
|
||||||
|
use tokio::time::{timeout, Duration};
|
||||||
|
|
||||||
pub fn socket_path() -> String {
|
pub fn socket_path() -> String {
|
||||||
std::env::var("MANGOTUNE_SOCKET").unwrap_or_else(|_| "/tmp/mangotune_preview.sock".into())
|
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);
|
log::info!("Socket API listening on {}", path);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
match listener.accept().await {
|
if should_quit(&state) {
|
||||||
Ok((stream, _addr)) => {
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
match timeout(Duration::from_millis(100), listener.accept()).await {
|
||||||
|
Ok(Ok((stream, _addr))) => {
|
||||||
let state = Arc::clone(&state);
|
let state = Arc::clone(&state);
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(err) = handle_connection(stream, state).await {
|
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);
|
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(
|
async fn handle_connection(
|
||||||
@@ -109,6 +122,10 @@ async fn handle_connection(
|
|||||||
let mut bytes = serde_json::to_vec(&response)?;
|
let mut bytes = serde_json::to_vec(&response)?;
|
||||||
bytes.push(b'\n');
|
bytes.push(b'\n');
|
||||||
write_half.write_all(&bytes).await?;
|
write_half.write_all(&bytes).await?;
|
||||||
|
|
||||||
|
if should_quit(&state) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -87,6 +87,16 @@ impl Runner {
|
|||||||
Ok(())
|
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.
|
/// Send SIGUSR1 to reload config in a running MangoHud process.
|
||||||
pub fn reload_config(pid: u32) -> Result<()> {
|
pub fn reload_config(pid: u32) -> Result<()> {
|
||||||
kill(Pid::from_raw(pid as i32), Signal::SIGUSR1)?;
|
kill(Pid::from_raw(pid as i32), Signal::SIGUSR1)?;
|
||||||
|
|||||||
+27
-12
@@ -150,6 +150,10 @@ impl PreviewScene {
|
|||||||
"MANGOTUNE_PREVIEW_VSYNC".to_string(),
|
"MANGOTUNE_PREVIEW_VSYNC".to_string(),
|
||||||
if studio.vsync { "1" } else { "0" }.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 let Some(running) = running {
|
||||||
if running.request.scene == PreviewScene::Studio {
|
stop_running_preview(running, &socket_path, &temp_config_path)?;
|
||||||
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);
|
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,10 +459,6 @@ impl PreviewController {
|
|||||||
state.current.take()
|
state.current.take()
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(running) = running {
|
|
||||||
let _ = Runner::stop(running.process);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (temp_path, socket_path) = {
|
let (temp_path, socket_path) = {
|
||||||
let Ok(state) = self.inner.lock() else {
|
let Ok(state) = self.inner.lock() else {
|
||||||
return Err(anyhow!("could not access preview state"));
|
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);
|
let layout_width = request.layout_width_override.unwrap_or(request.width);
|
||||||
write_preview_config(config, &temp_path, layout_width, request.scene)?;
|
write_preview_config(config, &temp_path, layout_width, request.scene)?;
|
||||||
if request.scene == PreviewScene::Studio {
|
if request.scene == PreviewScene::Studio {
|
||||||
@@ -503,7 +501,6 @@ impl PreviewController {
|
|||||||
if request.scene == PreviewScene::Studio {
|
if request.scene == PreviewScene::Studio {
|
||||||
wait_for_studio_socket(&socket_path, Duration::from_secs(3))
|
wait_for_studio_socket(&socket_path, Duration::from_secs(3))
|
||||||
.context("studio preview socket did not appear")?;
|
.context("studio preview socket did not appear")?;
|
||||||
apply_studio_runtime_settings(&socket_path, &request.studio)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let Ok(mut state) = self.inner.lock() else {
|
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(
|
fn write_preview_config(
|
||||||
config: &AnnotatedConfig,
|
config: &AnnotatedConfig,
|
||||||
path: &PathBuf,
|
path: &PathBuf,
|
||||||
|
|||||||
Reference in New Issue
Block a user